How to wait for async emits in Vue 3? - javascript

I'm triggering a submit event from a parent component, which emits an image data-url and a body text from two child components to the parent:
<ImgUploadComponent :triggerEmit="state.submit" #emitImage="getImage"/>
<EditorComponent :triggerEmit="state.submit" #emitBody="getBody"/>
The image and body are fetched with these functions:
const getImage = async (image) => {
form.image = await image
// arrives after storeRecord
}
const getBody = async (body) => {
form.body = await body
// arrives before storeRecord
}
and sent to the database in this function:
const storeRecord = async () => {
state.submit = true
await getImage
await getBody
console.log(form.image) // returns undefined
form.post('/admin/posts')
}
Problem is that the body arrives before form.post is sent but the image arrives after. When I wrote this function, it used to work but now it doesn't and I can't figure out why. How do you do it right?

Thanks to #Estus' suggestion, I was able to use a watcher to solve my problem:
const storeRecord = () => {
state.submit = true
watch(() => form.image, () => {
form.post('/admin/kreationen')
})
}
const getImage = (image) => {
form.image = image
}
const getBody = (body) => {
form.body = body
}

Related

useQuery always returning undefined data in react-query

I'm new to react-query and I'm trying to move all of my API calls into a new file, out of the useQuery calls.
Unfortunately when I do this all of my data is undefined.
I do see the network calls in the network tab, it just isn't being set properly in useQuery.
Thanks in advance for any help on how to change my code to fix this!
// this works
const { loading, data, error } = useQuery([conf_id], async () => {
const { data } = await axios.get(API_URL + '/event/' + conf_id)
return data
});
// this doesn't work - data is undefined
const axios = require('axios');
const getEventById = async () => {
const { data } = await axios.get(API_URL + '/event/2541' + '?noyear=true');
return data.data;
};
const { loading, data, error } = useQuery('conf_id', getEventById});
// the below variants don't work either
// const { loading, data, error } = useQuery('conf_id', getEventById()});
// const { loading, data, error } = useQuery('conf_id', async () => await getEventById()});
// const { loading, data, error } = useQuery('conf_id', async () => await
// const { data } = getEventById(); return data
// });
An AxiosResponse has a data attribute from which you can access the actual API JSON response.
Like you pointed out, this:
async () => {
const { data } = await axios.get(API_URL + '/event/' + conf_id)
return data
}
Should suffice for the fetching function.
So the final implementation should look like
const axios = require('axios');
const getEventById = async () => {
const { data } = await axios.get(API_URL + '/event/2541' + '?noyear=true');
return data;
};
const { loading, data, error } = useQuery('conf_id', getEventById);
The data you get from the useQuery should be undefined on the first render and once the server responds it will change to whatever the response is.

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);
};

if statement is not working while the condition is true

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;
}
}

React jsonserver promise result issue

I am creating a react/ redux app with json fake api server I am trying to add a login and trying to get data from json fake api server, data is showing and all ok , but data is always resulting as a promise and the required data is inside the promise. i tried many ways to distructure but throwing errors , could anyone help me on this,
my axios request
const urlss = "http://localhost:5000/users";
export const userslist = async () => {
const r = await axios.get(urlss);
const data = r.data;
return data;
};
const newout2 = userslist();
const newout = newout2;
console.log(newout);
the place where I am using it
export const login = (credentials) => (dispatch) => {
return new Promise((resolve, reject) => {
const matchingUser =
newout2 &&
newout2.find(({ username }) => username === credentials.username);
if (matchingUser) {
if (matchingUser.password === credentials.password) {
dispatch(setUser(matchingUser));
resolve(matchingUser);
} else {
dispatch(setUser(null));
reject("Password wrong");
}
} else {
dispatch(setUser(null));
reject("No user matching");
}
});
};
i am getting this error
You are using then in your userslist method while awaiting in an async method. drop the then and just use proper await inside an async method.
const urlss = "http://localhost:5000/users";
export const userslist = async () => {
const r = await axios.get(urlss);
const data = r.data;
return data;
};

Passing external data into components

In my react app, I am currently passing a list of stores by calling the API directly from the URL.
const getStore = async () => {
try {
const response = axios.get(
'http://localhost:3001/appointment-setup/storeList'
);
return response;
} catch (err) {
console.error(err);
return false;
}
};
I pass this function into my useEffect hook where I would set my get a list of stores using resp.data.stores:
const [storeLocations, setStoreLocations] = useState([]);
useEffect(() => {
async function getData(data) {
await service.stepLocation.init();
const resp = await getStore();
setStoreLocations(resp.data.stores);
}
setFlagRender(true);
return getData();
}, []);
This works, however, I noted in useEffect there is a call await service.stepLocation.init(). There is a file that already takes care of all the backend/data for the component.
const stepLocation = {
// removed code
// method to retrieve store list
retrieveStoreList: async function ()
let response = await axios.get(
constants.baseUrl + '/appointment-setup/storeList'
);
return response.data.stores;
,
// removed code
Since this data is available, I don't need the getStore function. However when I try to replace response.data.stores in useEffect with service.stepLocation.retrieveStoreList no data is returned. How do I correctly pass the data from this file in my useEffect hook?
I think your useEffect should be like follows as you want to save the stores in your state.
useEffect(() => {
const updateStoreLocations = async () => {
const storeLocations = await service.stepLocation.retrieveStoreList();
setStoreLocations(storeLocations);
}
updateStoreLocations();
}, [])

Categories