if statement is not working while the condition is true - javascript

I am learning to react js with typescript. The function I wrote uploads the images into firebase when the action submits button is clicked and it returns an array of URLs. Everything works fine but it takes a lot of time. In the if statement, I do not want to go to the if statement until the upload is complete since I need to put the URLs array into the product object. I have used promises and async-await but it is not working while the state is true.
If you want you can see the live code: https://codesandbox.io/s/eloquent-pine-tdv3wf?file=/src/AddProduct.tsx:1864-2218
This is the function that will set the state of the URLs into an array. Also, I have created promises after the success it will set true into the setSuccess state.
//States
const [success, setSuccess] = useState<boolean>(false)
const [images, setImages] = useState<any>([])
const [URLs, setURLs] = useState<any>([])
const uploadFiles = async (files: any) => {
const promises: any = []
files.map((file: any) => {
const sotrageRef = ref(storage, `files/${file.name}`);
const uploadTask = uploadBytesResumable(sotrageRef, file);
promises.push(uploadTask)
uploadTask.on(
"state_changed",
(snapshot: any) => {
const prog = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(prog);
},
(error: any) => console.log(error),
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("File available at", downloadURLs);
});
}
);
})
try {
await Promise.all(promises);
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
};
After clicking on submit action button this handleProductSubmit function will call and then it will call the uploadfiles function to set the uploaded files URLs into an array. If the success state is true then it will go to the next step for adding the URLs into my product data. **The problem is it goes to the next step because uploadWasSuccessful is true but my URLs array is still empty. After this, if statement my URLs state set the urls **
const handleProductSubmit = async (e: any) => {
e.preventDefault()
const uploadWasSuccessful: any = await uploadFiles(images)
console.log('uploadWasSuccessful', uploadWasSuccessful);
if (uploadWasSuccessful) {
const newProductValue = { ...productValue, URLs }
console.log(newProductValue, 'productValue');
}
}
The issue is here: setURLs state takes the URLs after rendering all my functions. Basically, it takes time to upload the files, and then it sets the URLs of the file.
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("File available at", downloadURLs);
});

uploadFiles is asynchronous, so you need to wait until everything is finished there before continuing:
const handleProductSubmit = async (e: any) => {
e.preventDefault()
const uploadWasSuccessful = await uploadFiles(images)
if (uploadWasSuccessful) {
const newProductValue = { ...productValue, URLs }
console.log(newProductValue, 'newProductValue');
}
}
Then change uploadFiles to return the result instead of setting state (unless you need that state in other places, in which case both set the state and return the value):
const uploadFiles = async (files: any) => {
// ... snip ...
try {
await Promise.all(promises);
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
}

Related

Canceling setTimeout early

I'm working on an audio recording class that either runs for an allotted period of time (such as 5 seconds) or can be stopped early by the user.
I'm using setTimeout to define the recording length, which works. However, I'm having trouble getting setTimeout working with a "stop" button. The error is as follows:
Cannot read properties of null (reading 'stop')
When the startRecording function executes, the handleStopRecording function is called which sets a timer with the "stopRecording" function. If the "stopRecording" function is called before the time elapses (by pressing the "stop" button), the function call that was initially in setTimeout will still execute when the timer expires, causing an error.
I tried fixing this by using clearTimeout, but then the "context" of the original function call is lost and we get the same error:
Cannot read properties of null (reading 'stop')
Unless I'm mistaken, I think this is an issue with closure of the setTimeout function - however I'm not sure how to clear the function early with a stop button and limit recording time.
Thank you in advance!
App.js (React.js)
import AudioRecorder from "./audioRecorder";
const App = () => {
const [recordedNameClipURL, setRecordedNameClipURL] = useState(null);
const [timeoutId, setTimeoutId] = useState(null);
const recorder = new AudioRecorder();
const startRecording = () => {
recorder.start();
handleStopRecording();
};
const handleStopRecording = async () => {
const id = setTimeout(stopRecording, 3000);
setTimeoutId(id);
};
const stopRecording = async () => {
clearTimeout(timeoutId);
const response = await recorder.stop();
setRecordedNameClipURL(response);
};
return (
...
);
};
audioRecorder.js
class AudioRecorder {
constructor() {
this.audioRecorder = null;
this.audioChunks = [];
}
initialize = async () => {
try {
await this.isSupported();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.audioRecorder = new MediaRecorder(stream);
this.audioRecorder.addEventListener("dataavailable", event => {
this.audioChunks.push(event.data);
});
} catch (err) {
console.log(err.message);
}
};
start = async () => {
try {
await this.initialize();
this.audioRecorder.start();
} catch (err) {
console.log(err.message);
}
};
stop = async () => {
try {
this.audioRecorder.stop();
const blob = await this.stopStream();
return URL.createObjectURL(blob);
} catch (err) {
console.log(err.message);
}
};
stopStream = () => {
return new Promise(resolve => {
this.audioRecorder.addEventListener("stop", () => {
const audioBlob = new Blob(this.audioChunks, {
type: this.audioRecorder.mimeType,
});
resolve(audioBlob);
});
});
};
isSupported = () => {
return new Promise((resolve, reject) => {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
resolve(true);
}
reject(new Error("getUserMedia not supported on this browser!"));
});
};
}
export default AudioRecorder;
Store the timer inside a React Ref instead
I usually store timeout/interval IDs in a React ref, because storing the handle isn't really "application state" the way that other things are. Sometimes it's needed to avoid render thrashing.
Here's what that looks like:
let timerRef = React.useRef(null)
const handleStopRecording = async () => {
timerRef.current = setTimeout(stopRecording, 3000)
}
const stopRecording = async () => {
clearTimeout(timerRef.current)
timerRef.current = null // good idea to clean up your data
const response = await recorder.stop()
setRecordedNameClipURL(response)
}
Code that needs to know if the timer is running should consult the timerRef; no additional state is needed:
let timerIsRunning = !!timerRef.current
You can try using a boolean value to check if the process is stopped. You can store it in state and change its value when starting or stopping
const [isStopped, setIsStopped] = useState(false);
const handleStopRecording = async () => {
const id = setTimeout(() => {
if(!isStopped){
stopRecording
}
}, 3000);
setTimeoutId(id);
};

Firebase image uploaded Urls are taking time to set on my state url state

I am learning to react js with typescript. The function I wrote uploads the images into firebase when the action submits button is clicked and it returns an array of URLs. Everything works fine but it takes a lot of time. In the if statement, I do not want to go to the if statement until the uploaded URls are set into the state because I need to add the URLs into my product object I have used promises and async-await but it is not working while the state is true.
If you want you can see the live code: https://codesandbox.io/s/eloquent-pine-tdv3wf?file=/src/AddProduct.tsx:1864-2218
This is the function that will set the state of the URLs into an array. Also, I have created promises after the success it will set true into the setSuccess state.
//States
const [success, setSuccess] = useState<boolean>(false)
const [images, setImages] = useState<any>([])
const [URLs, setURLs] = useState<any>([])
const uploadFiles = async (files: any) => {
const promises: any = []
files.map((file: any) => {
const sotrageRef = ref(storage, `files/${file.name}`);
const uploadTask = uploadBytesResumable(sotrageRef, file);
promises.push(uploadTask)
uploadTask.on(
"state_changed",
(snapshot: any) => {
const prog = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(prog);
},
(error: any) => console.log(error),
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("File available at", downloadURLs);
});
}
);
})
try {
await Promise.all(promises);
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
};
After clicking on submit action button this handleProductSubmit function will call and then it will call the uploadfiles function to set the uploaded files URLs into an array. If the success state is true then it will go to the next step for adding the URLs into my product data. **The problem is it goes to the next step because uploadWasSuccessful is true but my URLs array is still empty. After this, if statement my URLs state set the urls **
const handleProductSubmit = async (e: any) => {
e.preventDefault()
const uploadWasSuccessful: any = await uploadFiles(images)
console.log('uploadWasSuccessful', uploadWasSuccessful);
if (uploadWasSuccessful) {
const newProductValue = { ...productValue, URLs }
console.log(newProductValue, 'productValue');
}
}
The issue is here: setURLs state takes the URLs after rendering all my functions. Basically, it takes time to upload the files, and then it sets the URLs of the file.
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("File available at", downloadURLs);
});

Async / await problem, The Async / await function is returning true before the value is placed in the new state

I am creating a multiple image file upload using firebase and react-typescript. My main problem is with async/await. I have a function called uploadFiles which will save the downloaded URLs into my URLs state. But the problem is uploadFiles returning true while the URLs are still loading to set into the state.
My expectation is until setUrls state new values are placed into the setUrls state the Async / await will not return true.
I have explained in the video also, https://youtu.be/t6JqasRCPRM
Live code: https://codesandbox.io/s/eloquent-pine-tdv3wf?file=/src/AddProduct.tsx:2777-2941
The main problem is here: setURLs are taking time to set into the state.
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("2 File available at", downloadURLs);
});
Also, another problem is that promise is returning true while the URLs are still loading to set into the state.
try {
await Promise.all(promises);
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
UploadFiles function:
const uploadFiles = async (files: any) => {
const promises: any = []
files.map((file: any) => {
const sotrageRef = ref(storage, `files/${file.name}`);
const uploadTask = uploadBytesResumable(sotrageRef, file);
promises.push(uploadTask)
uploadTask.on(
"state_changed",
(snapshot: any) => {
const prog = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(prog);
},
(error: any) => console.log(error),
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("2 File available at", downloadURLs);
});
}
);
})
try {
await Promise.all(promises);
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
};
uploadWasSuccessful is returning true I want until the setURls is available my if statement will not go to the next step.
const handleProductSubmit = async (e: any) => {
e.preventDefault()
const uploadWasSuccessful: any = await uploadFiles(images) // returning true but the urls are still loading.
console.log('uploadWasSuccessful', uploadWasSuccessful);
console.log('Success', success);
if (uploadWasSuccessful) {
const newProductValue = { ...productValue, URLs }
console.log(newProductValue, 'productValue');
}
}
The setSuccess(true) call here is not asynchronous. This means its running immediately before the promises are resolved. Add await here to wait for the promises to resolve above.
try {
await Promise.all(promises);
await setSuccess(true); //await
return true;
} catch (e) {
console.error(e);
return false;
}
also make the uploads promises by including await here:
const sotrageRef = ref(storage, `files/${file.name}`);
const uploadTask = await uploadBytesResumable(sotrageRef, file); //added await
await promises.push(uploadTask) //added await
The trick I normally use is that the uploadTask it itself a promise already, so you can await it or chain its then, and just skip the approach with (at least) the third callback:
const uploadFiles = async (files: any) => {
const promises: any = []
files.map((file: any) => {
const storageRef = ref(storage, `files/${file.name}`);
const uploadTask = uploadBytesResumable(storageRef, file);
const urlTask = uploadTask.then(() => storageRef.getDownloadURL); // 👈
promises.push(urlTask); // 👈
uploadTask.on(
"state_changed",
(snapshot: any) => {
const prog = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(prog);
},
(error: any) => console.log(error),
() => { } // 👈
);
})
try {
const downloadURLs = await Promise.all(promises); // 👈
setURLs(downloadURLs); // 👈
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
};

How to write an asynchronous function

I'm doing my first ever react website and I need help to write an asynchronous JavaScript function.
Here I'm uploading the user input files to a firebase storage and then making a post request to the API to store the data on the database. However, since the firebase upload takes some time to upload the data to its storage, the API request happens before the upload finishes, therefore the data does not get uploaded to the db. Now I know I should use promises of async await keywords here, but I can't figure out how to. I'd appreciate if someone could help. Thanks in advance!
Here's the relevant code snippet:
const save = (items) => {
items.forEach((item) => {
const fileName = new Date().getTime() + item.label + item.file.name;
const uploadTask = storage.ref(`/items/${fileName}`).put(item.file);
uploadTask.on(
"state_changed",
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is" + progress + "% done.");
},
(err) => {
console.log(err)
},
() => {
storage.ref("items").child(fileName).getDownloadURL().then((url) => {
setSong((prev) => {
return { ...prev, [item.label]: url };
});
});
}
);
})
console.log(song)
axios.post("songs/create", song);
}
PS: Here, items is the array of input files from the user, each file is with a label and it is how the attributes are named on the json document.
setSong is a useState function. Here The JSON document already contains the other user inputs(which are not files), and the setSong method is used to append the firebase URLs of the files to it.
You have to wait for all files to get uploaded then you can call your API, in order to do that you should use Promise.all to wait to resolve all files :
const save = items => {
Promise.all(
items.map(item => {
return new Promise(resolve => {
const fileName = new Date().getTime() + item.label + item.file.name
const uploadTask = storage.ref(`/items/${fileName}`).put(item.file)
uploadTask.on(
'state_changed',
snapshot => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
console.log('Upload is' + progress + '% done.')
},
err => {
console.log(err)
},
() => {
storage
.ref('items')
.child(fileName)
.getDownloadURL()
.then(url => {
setSong(prev => {
return { ...prev, [item.label]: url }
})
resolve({[item.label]: url})
})
}
)
})
})
).then((res) => {
const song = {}
res.forEach(item => {
return {
...song,
...item
}
})
axios.post('songs/create', song)
})
}
Explanation
Functions and Async
Async/Await can be implemented wherever a function starts. Functions can be written in following forms:
function name(){};
function name() => {};
To write an async function, you would do the following:
async function name(){};
All of these functions are called functions though, to make functions run without calling them, we need to turn them into IIFE's, or Immediately Invoked Function Execution. If you want to create a function and execute it immediately you would surround the function in ()'s and end it with an ();.
(function () {})();
If we simplify this:
(() => {})();
Implementing async would go like this:
(async () => {})();
Await
The await operator is used to wait for a Promise, puting await in front of an expression that uses promises makes it wait for the promise. If it is used in front of an expression that doesn't use promises, it is redundant and your code editor/IDE will say so.
(async () => {
const str = await 'some string';
console.log(str);
})();
await is redundant here since the expression 'some string' does not relate to a promise, but a string.
(async () => {
const myPromise = new Promise((resolve, reject) =>
resolve('some string')
);
const str = await myPromise.then(x => console.log(x));
})();
await is properly used here since the expression myPromise is related to a promise that outputs a string.
Implementation
I'm not 100% sure how the api works within promises, I recommend you figure it out yourself, but this is my educated guess:
const save = (items) => {
Promise.all(
items.map((item) => {
return new Promise(async (resolve) => {
const fileName = new Date().getTime() + item.label + item.file.name;
const uploadTask = await storage
.ref(`/items/${fileName}`)
.put(item.file);
await uploadTask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is" + progress + "% done.");
},
(err) => {
console.log(err);
},
async () => {
await storage
.ref("items")
.child(fileName)
.getDownloadURL()
.then((url) => {
setSong((prev) => {
return { ...prev, [item.label]: url };
});
resolve({ [item.label]: url });
});
}
);
});
})
).then(async (res) => {
const song = {};
res.forEach((item) => {
return {
...song,
...item,
};
});
await axios.post("songs/create", song);
});
};

I am not sure what is blocking this test case, currently it goes into infinite loop

I need to run this test case but currently it gets stuck in an infinite loop. This is the minimalistic code, Any suggestion is appreciated.
Test.tsx file:
it('verify useDeleteExclusions', async () => {
deleteExclusionDevices.mockResolvedValue([])
const {result} = renderHook(() => useDeleteExclusions(['1234']), {
wrapper: AllTheProviders,
})
act(() => {})
await waitFor(() => {
expect(result.current).toEqual({"errorExclusionIds": [], "loading": false, "successExclusionIds": []})
})
})
})
Hook that needs to be tested:
export function useDeleteExclusions(exclusionIds) {
const [response, setResponse] = useState<any>([])
useEffect(() => {
async function deleteExclusionDevicesAsync(exclusionIds) {
const res = await deleteExclusionDevices(exclusionIds)
}
deleteExclusionDevicesAsync(exclusionIds)
}, [exclusionIds])
return { response }
}
Api call function (used by hook):
export async function deleteExclusionDevices(exclusionIds: any): Promise<any> {
const token = await readToken()
const response = []
return response
}
Test gets stuck like this:

Categories