I'm using React-dropzone-uploader
const [state,setState] = useState({document_blob_ids: [] })
const MyUploader = () => {
const getUploadParams = ({ meta }) => { // specify upload params and url for your files
return { url: '/v1/file_uploads/' }
}
const handleChangeStatus = ({ meta, file,xhr }, status) => { // called every time a file's `status` changes
console.log("handleStatus",status, meta, file)
if(status == "done") {
var json = JSON.parse(xhr.response)
var arr_blob_ids = state.documents_blob_ids.slice()
console.log("id added",json.blob.id)
if (json.blob.id){
arr_blob_ids.push(json.blob.id)
setState({...state,documents_blob_ids: arr_blob_ids})
}
}
else if(status == "removed") {
var json = JSON.parse(xhr.response)
var arr_blob_ids = state.documents_blob_ids.slice()
console.log("id removed",json.blob.id)
if (json.blob.id){
arr_blob_ids.filter( v => v!= json.blob.id)
setState({...state,documents_blob_ids: arr_blob_ids})
}
}
}
const handleSubmit = (files, allFiles) => { // receives array of files that are done uploading when submit button is clicked
console.log(files.map(f => f.meta))
allFiles.forEach(f => f.remove())
}
return (
<Dropzone
getUploadParams={getUploadParams}
onChangeStatus={(handleChangeStatus}
onSubmit={handleSubmit}
accept="image/*"
submitButtonContent = {null}
SubmitButtonComponent = {null}
/>
)
}
using the react-dropzone-uploader I'm trying to upload multiple files , But After a Successful upload of file since i'm updating the state , the component is rerendering which makes the loss of preview of uploaded files..
So i'm looking for an alternate solution, like storing all the id's in a global array and on clicking the submit button I can update it to state.
can someone guide me how to declare and use global array or any other alternate solution that solves my problems..
Related
technologies used: React Native / Expo / Firebase
I explain to you, I recover a list of key with my first onValue, in this one I forEach for each key and in this one I make another onValue in order this time to recover the information of the keys, but I realize that it creates a problem, one event calls another and it creates duplicate calls. Moreover I could not intervene on the second listening to delete it since it is only initialized locally, so how can I call this in the right way? thank you in advance for your answers, I'm new to react native with firebase!
Here is a snippet of the code:
useFocusEffect( // first time the screen is loaded, the gardener list is empty, so we need to get the gardener list from firebase
React.useCallback(() => {
setGardeners([])
const gardenerRef = ref(db, 'users/' + auth.currentUser.uid + '/gardeners');
const gardenerListener = onValue(gardenerRef, (snapshot) => {
const data = snapshot.val();
if (data != null) {
setIsFirst(false)
Object.keys(data).forEach(e => {
const temp = ref(db, 'gardeners/' + e);
onValue(temp, (snapshot) => {
const data = snapshot.val();
if (data != null && data.metadata != undefined) {
setGardeners(gardeners => [...gardeners, { id: e, name: data.metadata.name, isIrrig: data.irrig }]);
}
})
gardeners.length > 0 ? setCurrentGardener(gardeners[0].id) : setCurrentGardener("")
setC(currentGardener != "" ? currentGardener : "")
})
} else {
setIsFirst(true)
}
})
and here is the way i turn off the event during disassembly, for me it looks correct, now if you think i did it wrong, don't hesitate
return () => {
setGardeners([])
off(gardenerRef, gardenerListener)
// console.log("unmounted")
}
}, []))
I have an app that sends Images.
So at the first render, I have all the images shown in the frontend.
Then I send all the images in batch to x person.
And what I'm trying to do, is each time an image is sent, I want it to be deleted from the frontend.
So the client can see a kind of countdown of the images being sent and left.
I'm sending the setter of the useState to the backend, but each time it's being used isn't it supposed to refresh ?
I tried to put a button to see if the code works and yes it works fine, each time I click on it, its rerenders the images left correctly, does kind of refresh when I get the state I guess.
But how to do it automatically, refreshes each time an image is sent.
In the frontend where I display the images I did like so :
export default function Home() {
const [imageLinks, setImageLinks] = useState([""])
//Getting the image links set them to state and display them in the browser
const getImageLinks = async (cids) => {
cids.forEach((link) => {
let CIDImages = axios.get('https://ipfs.io/' + link)
.then(linkMetadata => {
let rawLinks = linkMetadata.data.image
let correctFormatLink = rawLinks.slice(7);
setImageLinks(oldArray => [...oldArray, correctFormatLink]);
})
})
}
//Images component
const images = () => {
return (
<>
{
imageLinks.map((link, index) => {
return <img src={`https://ipfs.io/ipfs/${link}`} alt="" key={index}/>;
}
)}
</>
)
}
useEffect(() => {
images()
}, [imageLinks, setImageLinks])
return (
{
images()
}
)
}
Then where I have my functions in a separate file :
export async function mint(setImageLinks, imageLinks, cids) {
//Sending images
let imageTower = [];
for (let i = 1; i < cids.length; i++) {
imageTower[i] = await tokenMinterFcn(cids[i])
let imageLink = await axios.get('https://ipfs.infura.io' + cids[i])
.then(linkMetadata => {
//Correct formay of the link
let rawLinks = linkMetadata.data.image
let correctFormatLink = rawLinks.slice(7);
// Filter to remove the link from imageLinks array
imageLinks.filter(imageLink => {
if (imageLink === correctFormatLink) {
imageLinks.splice(imageLinks.indexOf(imageLink), 1)
}
})
setImageLinks(imageLinks);
}).catch(err => {
console.log(err)
})
}
}
I have a file upload component. The behavior is simple: I send one upload request to the back-end per file and as the upload progress increase, I have a bar that should increase with it.
I have a state that holds every selected file and their respective progress, as such:
interface IFiles {
file: File;
currentProgress: number;
}
const [selectedFiles, setSelectedFiles] = useState<IFiles[]>([]);
And when the user clicks the upload button, this function will be triggered and call uploadFile for each file in my array state.
const sendFilesHandler = async () => {
selectedFiles.map(async (file) => {
const fileType = file.file.type.split('/')[0];
const formData = new FormData();
formData.append(fileType, file.file);
formData.append('filename', file.file.name);
await uploadFile(formData, updateFileUploadProgress);
});
};
Here is what the uploadFile function looks like.
const uploadFile = async (body: FormData, setPercentage: (filename: string, progress: number) => void) => {
try {
const options = {
onUploadProgress: (progressEvent: ProgressEvent) => {
const { loaded, total } = progressEvent;
const percent = Math.floor((loaded * 100) / total);
const fileName = body.get('filename')!.toString();
if (percent <= 100) {
setPercentage(fileName, percent)
}
}
};
await axios.post(
"https://nestjs-upload.herokuapp.com/",
body,
options
);
} catch (error) {
console.log(error);
}
};
As you can see, when uploadProgress is triggered it should inform call setPercentage function, which is:
const updateFileUploadProgress = (fileName: string, progress: number) => {
console.log('Entrada', selectedFiles);
const currentObjectIndex = selectedFiles.findIndex((x) => fileName === x.file.name);
const newState = [...selectedFiles];
newState[currentObjectIndex] = {
...newState[currentObjectIndex],
currentProgress: progress,
};
setSelectedFiles(newState);
console.log('Saída', newState);
};
And this function should only update the object of my state array where the filenames match. However, it is overriding the whole thing. The behavior is as follows:
So it seems that everything is fine as long as I am updating the same object. But in the moment onUploadProgress is triggered to another object, selectedFiles states becomes its initial state again. What am I missing to make this work properly?
I am not sure what is the exact reason behind this behaviour but I came up with the below unexpectedly simple solution after spending 3 hours straight on it.
const updateFileUploadProgress = (fileName: string, progress: number) => {
setSelectedFiles(prevState => {
const currentObjectIndex = prevState.findIndex((x) => fileName === x.file.name);
const newState = [...prevState];
newState[currentObjectIndex] = {
...newState[currentObjectIndex],
currentProgress: progress,
};
return newState;
})
};
I was able to mimic your problem locally, and solved it with the above approach. I think it was because the function (for some reason) still referenced the initial state even after rerenders.
In a chat application built in React, my intention is store the chat messages in localStorage. Following is the code for your reference
const [textMessages, setTextMessages] = useState([]);
const [textValue, setTextValue] = useState('')
useEffect(() => {
localStorage.getItem('messages') // Doesn't load the stored data
}, []);
const sendMessage = (e) => {
e.preventDefault();
if (textValue != "") {
const newData = []
newData.push([...textMessages, textValue])
setTextMessages([...textMessages, textValue]);
localStorage.setItem('messages', JSON.stringify(newData))
setTextValue("");
} else {
return;
}
};
return (
<>
<button type="submit" onClick={sendMessage}>
Send Message
</button>
</>
)
What could be the best solution? Here is the codesandbox link: https://codesandbox.io/s/upbeat-montalcini-bpdsp
You need to parse the JSON string back into an array, and set your state:
useEffect(() => {
const savedMessages = JSON.parse(localStorage.getItem('messages'))
if (savedMessages !== null) {
setTextMessages(savedMessages)
}
}, []);
Other things that are worth thinking about but I won't include in the simple example:
Consider wrapping the JSON.parse() in a try-catch, just in case the user has modified their local storage and it is now invalid JSON.
Having the local storage key stored as a const prevents typos when setting/getting the item from multiple places (E.g. const storageKey='messages'; localStorage.getItem(storageKey))
I am using the react-dropzone library to drag or select files from the browser.
I have the below component to let the user select multiple files. Everything works except that if the user chooses more files let say 2 or more files with size 1 MB, the selection of documents is taking time. If the number of files is more, the more time it takes to selected the user-selected files.
As per my reading of the react-dropzone docs, it processes files as soon as it uploads. So I tried setting autoProcessQueue='false'.
but no luck.
I just want to let the user select all the files without blocking for 30 or 40 secs after selecting files, and before send to the backend server.
I did the debug and the setSelectedUserFiles() is reached after all the files are processed internally by the DropZone. I am not sure if there is a way to disable it and let it process as part final submission of the form or click on the button.
In case if we can't achieve the same, is there a way to show the message to the user that files are being attached.
Any help will be appreciated.
Below is my react component
const SelectUserFiles = () => {
const [userName,setUserName] = userState('TestUser')
const [selectedUserFiles,setSelectedUserFiles] = userState([])
const handleUserFileUpload = async (acceptedFiles) => {
await setSelectedUserFiles(acceptedFiles)
}
return (
<div className='myClass'>Select Files</div>
<Dropzone
//autoProcessQueue='false'
accept={'.pdf'}
onDrop={acceptedFiles => handleUserFileUpload(acceptedFiles)}
>…</Dropzone>
</div>
<MyButton>
//logic to send the files to backend axios with the files selectedUserFiles
</MyButton>
)
}
Try something like this
Create separate api component that only send files,
Your component that has dropzone manages file upload
locally without sending to the server. You only click the button
and send it.
this link was very helpful:
https://www.robinwieruch.de/react-hooks-fetch-data
first component:
export const useApiCall = () => {
const [data, setData] = useState({});
const [selectedAllUserFiles, setSelectedAllUserFiles] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const pushToserver = async () => {
setIsError(false);
setIsLoading(true);
try {
const formData = new FormData();
Array.from(selectedAllUserFiles).forEach((file) => {
form.append('fileData', file);
});
const result = await axios.post(someUrl, formData);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
if (selectedAllUserFiles.length > 0) {
pushToserver();
}
}, [selectedAllUserFiles]);
//passing reference to the calling component
return [{ data, isLoading, isError }, setSelectedAllUserFiles];
};
Second component:
const SelectUserFiles = () => {
const [userName,setUserName] = userState('TestUser')
const [selectedUserFiles,setSelectedUserFiles] = userState([])
//Ref from logical api call component
const [{ data, isLoading, isError }, setSelectedAllUserFiles] = useApiCall();//new component
const handleUserFileUpload = async (acceptedFiles) => {
if (acceptedFiles) {
acceptedFiles.map((file) => {
setSelectedUserFiles((selectedUserFiles) => selectedUserFiles.concat(file));
return selectedUserFiles;
});
}
//await setSelectedUserFiles(acceptedFiles)
}
return (
<div className='myClass'>Select Files</div>
<Dropzone
//autoProcessQueue='false'
accept={'.pdf'}
onDrop={acceptedFiles => handleUserFileUpload(acceptedFiles)}
>…</Dropzone>
</div>
//on click call api and pass collected user files all together
<MyButton onClick={()=>setSelectedAllUserFiles(selectedUserFiles)}>
//logic to send the files to backend axios with the files selectedUserFiles
</MyButton>
)
}