I have a function handleFileSelect inside a React Component. It does some processing on the input file, and based on that it updates some states, which would trigger a rerender of the component.
But rerenders cause the function to be re-created. So will all the previous processed info be lost?
So should I use something like useCallback to prevent re-creation of my function?
And if that is the case, should'nt I do this for most functions?
const handleFileSelect = async(event: ChangeEvent < HTMLInputElement > ) => {
if (event.target.files && event.target.files ? .length > 0) {
setFormDisabled(true); // state-update
const file = event.target.files[0];
if (!imageWithinSizeLimit(file)) {
sendToast('error', 'File size beyond allowed range! Choose a file lesser than 0.5MB')
clearImageInput();
setFormDisabled(false); // state-update
return;
}
const valid = await validateImageType(file);
if (valid) {
const fileReader = new FileReader();
fileReader.readAsDataURL(file)
fileReader.onload = function(ev) {
// #ts-ignore
imagePreviewRef.current ? .setAttribute("src", ev.target.result)
}
setImageValid(true); // state-update
setFormDisabled(false) // state-update
}
else {
clearImageInput();
sendToast('error', "We only accept PNG or JPEG files as Avatar images")
setFormDisabled(false) // state-update
}
}
}
Simple answer: No.
If you don't need to track your function as dependency or you don't create component inside another component, then you don't need useCallback
Related
How can I save images on Wix Database Collection with also other data witch is in the same module?(the images are optional to upload) I have tried several ways, but the only results that I achieved are:
When adding an image to the database it add the image on a new row
When trying to add the image within the "nuovoOrdine" object it doesn't work
.
import wixData from 'wix-data';
import wixLocation from 'wix-location';
import {addOrderInfoVideo} from 'backend/creazioneVideoAdClassico'
export function button4_click(event) {
if ($w('#input2').value != "") {
var image1url;
var image2url;
var nuovoOrdine;
var isFinished = false;
// This way it saves the image on the Database but on a new element.. it should add this image on the same element that have the data below ("nuovoOrdine")
// if ($w('#uploadButton4').value.length > 0) {
// $w('#uploadButton4').startUpload().then((uploadedFile)=>{
// image1url = uploadedFile.url
// nuovoOrdine = {
// 'fileNecessari1': image1url
// }
// isFinished = true
// addOrderInfoVideo(nuovoOrdine)
// })
// } else {
// if ($w('#uploadButton5').value.length > 0) {
// isFinished = false
// } else {
// isFinished = true
// }
// }
nuovoOrdine = {
// this should return the uploaded image link but it doesn't
'fileNecessari1': $w('#uploadButton5').startUpload().then((uploadedFile)=>{
return uploadedFile.url
}),
'puntiDiForza': $w('#textBox1').value,
'numeroOrdine': $w('#input2').value,
'colori': $w('#textBox2').value,
'idea': $w('#textBox3').value,
'link': $w('#input3').value,
'nomeProdotto': $w('#textBox3DAC315').value,
'concorrenza': $w('#textBox3DAC315DAC326').value,
'contatti': $w('#input1').value,
'dettagliExtra': $w('#textBox4').value,
'slider1': $w('#slider1').value,
'slider2': $w('#slider2').value,
'slider3': $w('#slider3').value,
'slider4': $w('#slider4').value,
'slider5': $w('#slider5').value,
'slider6': $w('#slider6').value,
'slider7': $w('#slider7').value,
'slider8': $w('#slider8').value,
'slider9': $w('#slider9').value,
'slider10': $w('#slider10').value,
'slider11': $w('#slider11').value,
'slider12': $w('#slider12').value,
'slider13': $w('#slider13').value
};
addOrderInfoVideo(nuovoOrdine)
// wixLocation.to('https://www.mywebsite.it/ringraziamenti-video-ads')
} else {
let textError = "Inserisci la mail con la quale hai effettuato l'ordine"
$w('#text133').text = textError
$w('#text133').text.bold()
}
}
Well, we can't really see your data operations, so it's a bit hard to tell, but I definitely see at least one thing you're doing wrong.
On this line over here, you're not waiting for your promise to resolve, so the property will not have the value you are looking for:
'fileNecessari1': $w('#uploadButton5').startUpload().then((uploadedFile)=>{
return uploadedFile.url
}),
Instead try something like this.
First add an async to you event handler function:
export async function button4_click(event) {
Then, do the image upload and wait for it to finish:
const uploadedFile = await $w('#uploadButton5').startUpload();
const uploadedUrl = uploadedFile.url;
Finally, create your object:
nuovoOrdine = {
'fileNecessari1': uploadedUrl,
'puntiDiForza': $w('#textBox1').value,
...
Currently i'm doing a quiz composed by multiple categories that can be chosen by the user and i wanna check if the user responded to all questions. For doing that, i compared the number of questions he answered with the number of questions gived by the api response. The problem is that i have an "submit answers" button at the end of the last question, with that onClick function:
const sendAnswers = (e, currentQuiz) => {
setQuizzes({...quizzes, [currentQuiz]:answers});
setAnswers([])
var answeredToAllQuestions = true
DataState.map(function (quiz) {
if(quiz.category in quizzes){
if(Object.keys(quiz.questions).length !== Object.keys(quizzes[quiz.category]).length){
answeredToAllQuestions=false;
}
}
});
if(answeredToAllQuestions === false){
setAlertTrigger(1);
}
else{
setNumber(number+1);
}
}
in that function i use setState on this line: setQuizzes({...quizzes, [currentQuiz]:answers}); to upload the answers he checked on the last question before checking if he answered to all questions. The problem is that state of quizzes is not updated imediatly and it s not seen by the if condition.
I really don't know how am i supposed to update the state right after setting it because, as i know, react useState updates the state at the next re-render and that causes trouble to me..
Considering that quizzes will be equal to {...quizzes, [currentQuiz]:answers} (after setQuizzes will set it), there is no reason to use quizzes in if condition. Replace it with a local var and problem will be solved.
const sendAnswers = (e, currentQuiz) => {
let futureValueOfQuizzes = {...quizzes, [currentQuiz]:answers}
setQuizzes(futureValueOfQuizzes);
setAnswers([])
var answeredToAllQuestions = true
DataState.map(function (quiz) {
if(quiz.category in futureValueOfQuizzes){
if(Object.keys(quiz.questions).length !== Object.keys(quizzes[quiz.category]).length){
answeredToAllQuestions=false;
}
}
});
if(answeredToAllQuestions === false){
setAlertTrigger(1);
}
else{
setNumber(number+1);
}
}
I would like to take this opportunity to say that these type of problems appear when you use React state for your BI logic. Don't do that! Much better use a local var defined in components body:
const Component = () => {
const [myVar , setMyVar] = useState();
let myVar = 0;
...
}
If myVar is used only for BI logic, use the second initialization, never the first!
Of course sometimes you need a var that is in BI logic and in render (so the state is the only way). In that case set the state properly but for script logic use a local var.
You have to either combine the useState hook with the useEffect or update your sendAnswers method to perform your control flow through an intermediary variable:
Using a temporary variable where next state is stored:
const sendAnswers = (e, currentQuiz) => {
const newQuizzes = {...quizzes, [currentQuiz]:answers};
let answeredToAllQuestions = true
DataState.map(function (quiz) {
if(quiz.category in newQuizzes){
if (Object.keys(quiz.questions).length !== Object.keys(newQuizzes[quiz.category]).length){
answeredToAllQuestions = false;
}
}
});
setQuizzes(newQuizzes);
setAnswers([]);
if (answeredToAllQuestions === false) {
setAlertTrigger(1);
} else {
setNumber(number+1);
}
}
Using the useEffect hook:
const sendAnswers = (e, currentQuiz) => {
setQuizzes({...quizzes, [currentQuiz]:answers});
setAnswers([]);
}
useEffect(() => {
let answeredToAllQuestions = true
DataState.map(function (quiz) {
if(quiz.category in quizzes){
if (Object.keys(quiz.questions).length !== Object.keys(quizzes[quiz.category]).length){
answeredToAllQuestions = false;
}
}
});
if (answeredToAllQuestions === false) {
setAlertTrigger(1);
} else {
setNumber(number+1);
}
}, [quizzes]);
I have the following code for uploading multiple images in my React app. The problem is that console.log(e) prints Progress Event object with all its values, but when I want to update my state I still be the default values, null, 0, []. I understand that onload is asynchronous and that might be the reason they are not updated. Technically the code is working when I upload file one by one. When I select multiple files at once, only the first one is being displayed. What am I doing wrong here?
const [fileUpload, setFileUpload] = useState(null);
const [filesUploaded, setFilesUploaded] = useState([]);
const [filesUploadedCount, setFilesUploadedCount] = useState(0);
const handleFileUpload = (e) => {
if (filesUploadedCount === 5 || e.currentTarget.files > 5) {
return;
}
const files = e.currentTarget.files;
console.log(files.length);
console.log(e.currentTarget.files);
Array.from(files).forEach((file: any) => {
const reader = new FileReader();
reader.onload = (e) => {
console.log(e); // Progress Event {}
setFileUpload(e.target.result);
setFilesUploadedCount(filesUploaded.length + 1);
setFilesUploaded([...filesUploaded, e.target.result]);
console.log(fileUpload); // null
console.log(filesUploaded); // []
console.log(filesUploaded.length); // 0
console.log(filesUploadedCount); // 0
};
reader.readAsDataURL(file);
});
};
Here I display them.
{filesUploaded?.map((file, index) => {
return (
<ItemImage
key={index}
src={file}
handleRemoveFile={handleRemoveFile}
/>
);
})}
useState is also asynchronous operation, so you should not rely on their values for calculating the next state. Pass a function like this. You may not see in your console.log because of that.
setFilesUploaded(prevState => [...prevState, e.target.result]);
I have blobs in my Vuex Store, and I want to download the state of my application as.JSON and restore it.
In general, JSON.stringify(store.state,...) works great without blobs. I just don't know how I can store the Blobs into JSON.
Here is my code. The if(value instanceof Blob)-Block contains my attempt to convert the BLOB into JSON.
const s = JSON.stringify(store.state
, (key, value) => {
// let process = true;
if (typeof value === 'object') {
if(value instanceof Blob){
//I can't get this to work:
// const reader = new FileReader();
// // let process = false;
// reader.onload = function(event){
// console.log(JSON.stringify(reader.result));
// value = JSON.stringify(reader.result);
// return value;
// };
// reader.readAsText(value);
// process = false;
}
// Duplicate reference found, discard key
if (cache.includes(value)) return;
// Store value in our collection
cache.push(value);
}
if(process){
return value;
}
})
//download
const newBlob = new Blob([s], { type: 'application/json;' });
const filename = `jam-along-${new Date().getTime()}.json`;
saveAs(newBlob, filename);
cache = [];
}
Another idea was to use Vuex-Persist to download the store with a customized saveState: (key, state, storage) =>{..., but I can't get it to work. I don't want vuex-persist to do anything in the sessionStore or the localStorage, and I want to trigger saveState from outside without mutating the store.
Is there any way to download and restore my vuex store?
Thank you!
The below function handle uploaded files, for some reason the setFiles doesn't update the files list after the callback ends so it causes the previous uploaded file to show up on the page, for example the user uploaded an image 1.jpg, nothing will show up on the page, next the user uploads a second file- now the first image 1.jpg will show up, and so on.
On setFiles the state is correct and updated but the return doesn't update the files state.
Any idea why?
const [files, setFiles] = useState([])
const addFiles = addedFiles => {
const newFiles = Array.from(addedFiles, file => newFileDecorator(file))
setFiles([...files, ...newFiles])
newFiles.forEach(file => {
file.reader.onload = async () => {
const dimensions = await getImageDimensions(file.reader.result)
setFiles(state => {
const index = state.findIndex(f => f.id === file.id)
state[index].readyState = file.reader.readyState
state[index].dimensions = dimensions
return state
})
}
file.reader.readAsDataURL(file.data)
})
}
You are mutating state without creating a new reference for it, so React skips the update as the shallow comparison indicates that they are the same object. Use this pattern instead.
setFiles(state => {
const file = state.find(f => f.id === file.id)
file.readyState = file.reader.readyState
file.dimensions = dimensions
return [ ...state, file ]
})