React function running before state change - javascript

I have a function that is run when a user clicks a button, when this function is run it gathers data and updates state. I then have another function which runs that uses some of the data that is added to state, the issue is the state is not updating in time so the function is using old data.
First function
async function callWeather() {
const key = "";
// Get location by user
let location = formData.location;
// Url for current weather
const currentWeatherUrl = `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${key}`;
// Get the current weather
const currentWeatherResponse = await fetch(currentWeatherUrl);
if (!currentWeatherResponse.ok) {
// Return this message if an error
const message = `An error has occured: ${currentWeatherResponse.status}`;
throw new Error(message);
}
const weatherDataResponse = await currentWeatherResponse.json();
// Update state with data
setWeatherData(weatherDataResponse);
}
Second function
async function callForcast() {
const key = "";
// Get lat & lon from the previous data fetch
const lon = weatherData.coord.lon
const lat = weatherData.coord.lat
// Get forcast data
const forcastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
const forcastWeatherResponse = await fetch(forcastWeatherUrl);
if (!forcastWeatherResponse.ok) {
const message = `An error has occured: ${forcastWeatherResponse.status}`;
throw new Error(message);
}
const forcastDataResponse = await forcastWeatherResponse.json();
// Update state with the forcast data
setForcastData(forcastDataResponse);
}
This then runs with the onClick calling both functions
function callWeatherAndForcast() {
callForcast();
callWeather();
}

use 'await' before calling callForcast so the second function (callWeather) does'nt get called immediately after calling first function.
async function callWeatherAndForcast() {
await callForcast();
callWeather();
}
also as #tromgy mentioned in the comments, React state updates are not immediate, try calling callWeather function inside a hook which has a dependency on forcastData state

Are you using FunctionComponent or Classes ?
Also, keep in mind that updating the state will trigger a rerendering. This means that:
The state update is not immediate
If one of your functions use the data from another, you should take care of these dependencies.
For helping you correctly, I need to know if you use FunctionComponent or Class and get the whole Function/Class.
Edit: based on the fact that you're using FunctionComponent.
In order to archive what you want, you need to use hooks.
Hooks are the way to handle a function component lifecycle.
For your problem, you'll need useState, useCallback hooks.
export const DisplayWeather = () => {
const [forecast, setForecast] = useState();
const [weather, setWeather] = useState();
const [error, setError] = useState();
const onSubmit = useCallback(async () => {
getWeather();
getForecast();
}, [forecast, weather]);
const getWeather = useCallback(async () => {
const key = "";
const location = formData.location;
const currentWeatherURL = `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${key}`;
const apiResponse = await fetch(currentWeatherURL);
if(!apiResponse.ok){
const message = `An error has occured: ${apiResponse.status}`;
setError(message);
} else {
const weatherData = apiResponse.json();
setWeather(weatherData);
}
}, [formData]);
const getForecast = useCallback(async () => {
const key = "";
const lon = weather.coord.lon;
const lat = weather.coord.lat;
const forecastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
const apiResponse = await fetch(forecastWeatherUrl);
if(!apiResponse.ok) {
const message = `An error has occured: ${apiResponse.status}`;
setError(message);
} else {
const forecastData = apiResponse.json();
setForecast(forecastData);
}
}, [weather]);
if(error){
return (
<p>Error: {error}</p>
)
}
return (
<p>Forecast data</p>
<p>{forecast.data.temperature}</p>
<p>Weather data</p>
<p>{weather.data.temperature}</p>
);
}
In the code above, I set 2 state variables (weather & forecast) and create 3 functions.
The onSubmit function is called when the user click. His callback depend on two variables (weather & forecast) which are referenced in the dependency array (the [] after the callback)
The getWeather function is called before getForecast because the result of the getForecast function depends on the weather state. That's why you have weather in the getForecast callback dependency array. It tells getForecast that when the value of weather change, it needs to re-render.
Note that i've added formData into the dependency array of getWeather otherwise, when the user click, the getWeather function won't get any value from formData.
Note: it is not a working example, just a simple explanation. You can find more infos here:
Hooks Reference
useCallback Reference

State does not update immediately! Meaning that the function I want to get the new state will get the previous state. To fix this I added callForcast function into a useEffect hook which has a dependency on callWeather because callForcast needs callWeather to update state first. This means when this function is run state will be updated in time.
useEffect (() => {
async function callForcast() {
const key = "";
// Get lat & lon from the previous data fetch
const lon = weatherData.coord.lon
const lat = weatherData.coord.lat
// Get forcast data
const forcastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&units=metric&appid=${key}`
const forcastWeatherResponse = await fetch(forcastWeatherUrl);
if (!forcastWeatherResponse.ok) {
const message = `An error has occured: ${forcastWeatherResponse.status}`;
throw new Error(message);
}
const forcastDataResponse = await forcastWeatherResponse.json();
// Update state with the forcast data
setForcastData(forcastDataResponse);
}
// Call the callForcast function to run
callForcast();
},
// This effect hook is dependent on callWeather
[callWeather])
Now my onClick will only need to call callWeather()
function.
Thanks to:
#Mohammad Arasteh
#Thomas Geenen
#tromgy

I think you should try to call callWeather(); under callForcast() after setForcastData() state set, and if update state value not affected in call weather you can try to add wait in setForcastData().
Or, try to add wait before callForcast() in callWeatherAndForcast() onClick

Related

State doesn't update immediately in React

I know I have to use useEffect but I couldn't figure out a way to make this code to work. I want the state courses to be updated immediately every single time I'm adding a new course. The data variable in the getCourses function is updated, but the update of the state doesn't happen immediately.
function CourseList() {
const [courses, setCourses] = useState([]);
const [idProf, setIdProf] = useState();
this function gets the list of courses from the database and store it in courses
const getCourses = async () =>{
const response = await fetch(`${SERVER}/api/courses`);
const data = await response.json();
setCourses(data);
// -> here setCourses doesnt update the state, but data has the updated array of courses
}
this function adds a course and then adds the professor stored in idProf to this course
const addCourse = async(course) => {
//...fetch with post method...//
getCourses(); // here i call the method but when I'm trying to add the professor to the course on the next line, the state isn't updated yet
addStudentCourse(idProf, course.cod);
}
I tried to use useEffect but it s updating the state only one time
useEffect(()=>{
getCourses();
},[]);
the courses are not updated when this function is running
const addStudentCourse = async(idStudent,codCourse)=>{
let id = -1;
//searching for the id in courses
for(let c of courses){
if(c.cod == codCourse){
id = c.id;
}
}
console.log(id)// id is still -1, it cant find the course because the state wasnt updated yet
if(id != -1){
//..adding to the database..//
}
}
return (<div></div>)
}
Your getCourses funciton is async, but you are not awaiting it. Try await getCourses().

Fetching all the available microphones - Map is not a function

I have the following code to fill up a select with the available microphones
const audioInputSelect = document.querySelector('select#audioSource');
// Updates the select element with the provided set of cameras
function updateMicrophoneList(microphones) {
console.log(microphones);
audioInputSelect.innerHTML = '';
microphones.map(microphone => {
const microphoneOption = document.createElement('option');
microphoneOption.label = microphone.label;
microphoneOption.value = microphone.deviceId;
}).forEach(microphoneOption => audioInputSelect.add(microphoneOption));
}
// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
// Get the initial set of cameras connected
const microphonesList = getConnectedDevices('audioinput');
updateMicrophoneList(microphonesList);
// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
const newMicrophoneList = getConnectedDevices('audioinput');
updateMicrophoneList(newMicrophoneList);
});
I'm getting the error
VM1759 audio_devices.js:7 Uncaught TypeError: microphones.map is not a function
at updateMicrophoneList (VM1759 audio_devices.js:7)
at VM1759 audio_devices.js:24
Why doesn't map work here?
getConnectedDevices is an async function, meaning that it returns a Promise instead of an array. You can use the .then function to update the list when the Promise is fulfilled.
getConnectedDevices('audioinput').then(updateMicrophoneList);

Firebase pagination problem, how to set the latest document reference correctly?

I am trying to perform infinite scroll in react component, but after all the data loads latestMealDoc becomes undefined.
Also same thing happens when I go to different route and come back to the component, the latest document is incorrect and I start getting the same items all over again.
Am i setting the state wrong?
const [latestMealDoc, setLatestMealDoc] = useContext(latestMealDocContext);
const getNextMeals = async () => {
const ref = db
.collection("meals")
.orderBy("timestamp")
.limit(6)
.startAfter(latestMealDoc || 0);
const data = await ref.get();
data.docs.forEach((doc) => {
const meal = doc.data();
setMealSearchResults((prev: any) => [...prev, meal]);
});
setLatestMealDoc(data.docs[data.docs.length - 1]);
};
useEffect(() => {
getNextMeals();
}, []);
Can you log the output of latestMealDoc?
What are you expecting to see for latestMealDoc?
It sounds like the undefined variable has not been assigned a value yet.
A variable that has not been assigned a value is of type undefined. A method or statement also returns undefined if the variable that is being evaluated does not have an assigned value. A function returns undefined if a value was not returned.

Websockets in Sapper

I have a readable store in Svelte that looks like this:
const state = {};
export const channels = readable(state, set => {
let st = state;
let socket = new WebSocket("ws://127.0.0.1:5999");
socket.onmessage = function (event) {
var datastr = event.data.split(':');
st[datastr[0]].value = datastr[1];
st[datastr[0]].timestamp = Date.now();
set(st)
};
return () => {
socket.close()
}
});
When I import it to my Svelte App works. But if I put that App.svelte as my index.svelte running on Sapper, it doesnt work at first. It says error 500 websocket is not defined. Once I reload the page in the browser start to work...
I have try to parse a function that creates the store instead:
export const getChannel = () => {
// here my store
return {...store}
}
and then creating the store inside a onMount() like this:
onMount( ()=> {
const channel = getChannel();
});
But doesnt seem to do the trick... What do I miss?
Note: If a just replace the store by a simple writable, and create the websocket onMount(), it works without any problem. I just only wanted to put all the communication inside the store as a readable...
In Sapper, code in components (or imported into components) is executed in Node during server-side rendering unless it's put inside onMount (which doesn't run on the server, because there's no 'mounting' happening) or an if (process.browser) {...} block, or something equivalent.
That includes things like references to $channels causing channels.subscribe(...) to be called during initialisation.
Since there's no WebSocket global in Node, creating that subscription will fail. The simplest workaround is probably a simple feature check:
const state = {};
export const channels = readable(state, (set) => {
if (typeof WebSocket === 'undefined') return;
let st = state;
let socket = new WebSocket("ws://127.0.0.1:5999");
socket.onmessage = function (event) {
var datastr = event.data.split(":");
st[datastr[0]].value = datastr[1];
st[datastr[0]].timestamp = Date.now();
set(st);
};
return () => {
socket.close();
};
});

How do I tell React component to change its state when an event is triggered from third party lib?

I am using createjs to create an canvas, and there is a double click event in this canvas, now I want to tell a React component when the event is triggered, and set state to a variable that defined in the custom function. for example, I want to set the value to 'pose.position' that's in the custom function.
my react component:
const [value, setValue] = useState(null);
const handleEvent = event => {
console.log(value);
};
useEffect(() => {
const ros = new ROSLIB.Ros({
url
});
const viewer = new ROS2D.Viewer({
divID,
width,
height
});
const nav = NAV2D.OccupancyGridClientNav({
ros,
rootObject: viewer.scene,
viewer,
serverName,
continuous
});
setValue(nav.position);
const canvas = divEl.current.children[0];
canvas.addEventListener("dblclick", handleEvent, false);
return () => {
canvas.removeEventListener("dblclick", handleEvnet);
};
}, []);
return <div id={divID} ref={divEl} />;
and the custom function:
this.rootObject.addEventListener("dblclick", function(event) {
// convert to ROS coordinates
const coords = stage.globalToRos(event.stageX, event.stageY);
const pose = new ROSLIB.Pose({
position: new ROSLIB.Vector3(coords)
});
// send the goal
sendGoal(pose);
that.position = pose.position;
// that.mouseClick = true;
console.log("clicked");
});
//I do not know how my React component can get this value, so I just return
return this.position;
The problem is the value in component only set once,and the log function in handleEvent outputs null, the value will not be updated.How do I notify React that the value is changed? Should I create another useEffect(()=>{}, [value])?
OR
Can I dispatch an action in this.rootObject.addEventListener so I can notify react to re-render some components? Can I use redux in non-react function?
From what I see there are 2 options to get this working:
1) Put the part that calculates position to the React component:
const [value, setValue] = useState(null);
const handleEvent = event => {
// convert to ROS coordinates
const coords = stage.globalToRos(event.stageX, event.stageY);
const pose = new ROSLIB.Pose({
position: new ROSLIB.Vector3(coords)
});
setValue(pose.position);
};
2) Introduce an extra event and listen for it in the component. So that your custom function will look something like this:
this.rootObject.addEventListener("dblclick", function(event) {
// convert to ROS coordinates
const coords = stage.globalToRos(event.stageX, event.stageY);
const pose = new ROSLIB.Pose({
position: new ROSLIB.Vector3(coords)
});
// send the goal
sendGoal(pose);
this.dispatchEvent(new CustomEvent('newposition', { detail: pose.position }));
}
and then add an extra line in your React component:
canvas.addEventListener("newposition", (e) => setValue(e.detail), false);
In both cases, you will need to add value as a second argument to useEffect to prevent re-renders.

Categories

Resources