Reputation: 679
This function component has a template method that calls onChangeHandler
, which accepts a select
value and updates state
. The problem is, state
does not update until after the render method is called a second time, which means the value of selected option is one step ahead of the state
value of selectedRouteName
.
I know there are lifecycle methods in class components that I could use to force a state
update, but I would like to keep this a function component, if possible.
As noted in the code, the logged state of selectedRouteDirection
is one value behind the selected option. How can I force the state
to update to the correct value in a functional component?
This question is not the same as similarly named question because my question asks about the actual implementation in my use case, not whether it is possible.
import React, { useState, Fragment, useEffect } from 'react';
const parser = require('xml-js');
const RouteSelect = props => {
const { routes } = props;
const [selectedRouteName, setRouteName] = useState('');
const [selectedRouteDirection, setRouteDirection] = useState('');
//console.log(routes);
const onChangeHandler = event => {
setRouteName({ name: event.target.value });
if(selectedRouteName.name) {
getRouteDirection();
}
}
/*
useEffect(() => {
if(selectedRouteName) {
getRouteDirection();
}
}); */
const getRouteDirection = () => {
const filteredRoute = routes.filter(route => route.Description._text === selectedRouteName.name);
const num = filteredRoute[0].Route._text;
let directions = [];
fetch(`https://svc.metrotransit.org/NexTrip/Directions/${num}`)
.then(response => {
return response.text();
}).then(response => {
return JSON.parse(parser.xml2json(response, {compact: true, spaces: 4}));
}).then(response => {
directions = response.ArrayOfTextValuePair.TextValuePair;
// console.log(directions);
setRouteDirection(directions);
})
.catch(error => {
console.log(error);
});
console.log(selectedRouteDirection); // This logged state is one value behind the selected option
}
const routeOptions = routes.map(route => <option key={route.Route._text}>{route.Description._text}</option>);
return (
<Fragment>
<select onChange={onChangeHandler}>
{routeOptions}
</select>
</Fragment>
);
};
export default RouteSelect;
Upvotes: 3
Views: 4315
Reputation: 875
Well, actually.. even though I still think effects are the right way to go.. your console.log
is in the wrong place.. fetch
is asynchronous and your console.log
is right after the fetch
instruction.
As @Bernardo states.. setState is also asynchronous
so at the time when your calling getRouteDirection();
, selectedRouteName
might still have the previous state.
So to make getRouteDirection();
trigger after the state was set.
You can use the effect and pass selectedRouteName
as second parameter (Which is actually an optimization, so the effect only triggers if selectedRouteName
has changed)
So this should do the trick:
useEffect(() => {
getRouteDirection();
}, [selectedRouteName]);
But tbh.. if you can provide a Stackblitz or similar, where you can reproduce the problem. We can definitely help you better.
Upvotes: 1
Reputation: 71
setState is asynchronous! Many times React will look like it changes the state of your component in a synchronous way, but is not that way.
Upvotes: 0