I have a component with the following situation:
These are two methods when i select two teams (Home, Away) in two select components
const selectHomeTeamStat = evt => {
const { value } = evt.target;
setSelectedHomeOption(value);
getStats(leagueId, value, 'home');
};
const selectAwayTeamStat = evt => {
const { value } = evt.target;
setSelectedAwayOption(value);
getStats(leagueId, value, 'away');
};
As you can see i am able to pass this string parameter 'Home' and 'Away' to the getStats request in order to differentiate and create then two different states
After that i have realized that instead of the strings 'Home' and 'Away' i actually need the state name of the teams to pass as parameter to the getStats request
So this changed to
const [selectedHomeName, setSelectedHomeName] = useState("");
const [selectedAwayName, setSelectedAwayName] = useState("");
const selectHomeTeamStat = evt => {
const { value } = evt.target;
const item = items.find(item => item.team_id == value);
setSelectedHomeOption(value);
setSelectedHomeName(item.name);
console.log('Home Team Name:', selectedHomeName);
getStats(leagueId, value, selectedHomeName);
};
const selectAwayTeamStat = evt => {
const { value } = evt.target;
const item = items.find(item => item.team_id == value);
setSelectedAwayOption(value);
setSelectedAwayName(item.name);
console.log('Away Team name:', selectedAwayName);
getStats(leagueId, value, selectedAwayName);
};
item.name returns correctly the name of the team selected so then i expect to see selectedHomeName and selectedAwayName states too but in my console.log i don't see the it
console.log('Home Team Name:', selectedHomeName); => Team Name:
console.log('Away Team Name:', selectedAwayName); => Away Name:
So the question is, what am i doing wrong? how can i pass these states to getStats inside the event handler in order to differentiate?
That's logging the previous value, as that was the one in scope when the function was defined, before calling it.
If you actually want to log the right value that is going to eventually (as setState is async) make it into the state on that same spot, simply log item.name.
Alternatively, if you want to log the value only after it's already in the state, then you should use useEffect, passing it the values you want to react to as deps:
// We want React to call this function every time `selectedAwayName` changes:
useEffect(() => {
console.log('Away Team name:', selectedAwayName);
}, [selectedAwayName]);
Here you can see what is going on and how useEffect fixes it:
const App = () => {
const [selectedHomeName, setSelectedHomeName] = React.useState('');
const [selectedAwayName, setSelectedAwayName] = React.useState('');
// When the component renders for the first time, this function is created,
// and the `selectedHomeName` value that will log when called is the one
// currently in scope, that is, an empty string.
// When the component re-renders, a new function is created again with the
// value currently in scope, which is the one we set previously. When changed
// again, it will log the previous value, not the new one:
const selectHomeTeamStat = ({ target }) => {
setSelectedHomeName(target.textContent);
console.log('PREVIOUS Home Team =', selectedHomeName);
};
const selectAwayTeamStat = ({ target }) => {
setSelectedAwayName(target.textContent);
console.log('PREVIOUS Away Team =', selectedAwayName);
};
// We are telling React to call this function every time `selectedHomeName` or
// `selectedAwayName` change:
React.useEffect(() => {
console.log(`CURRENT TEAMS = ${ selectedHomeName } / ${ selectedAwayName }`);
}, [selectedHomeName, selectedAwayName])
return (<React.Fragment>
<nav className="buttons">
<button onClick={ selectHomeTeamStat }>Home Team 1</button>
<button onClick={ selectHomeTeamStat }>Home Team 2</button>
<button onClick={ selectAwayTeamStat }>Away Team 1</button>
<button onClick={ selectAwayTeamStat }>Away Team 2</button>
</nav>
<div>{ selectedHomeName } / { selectedAwayName }</div>
</React.Fragment>);
}
ReactDOM.render(<App />, document.querySelector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
#app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.buttons {
display: flex;
margin: 32px 0;
}
button {
margin: 0 4px;
padding: 8px;
border: 2px solid black;
background: transparent;
cursor: pointer;
border-radius: 2px;
}
.as-console-wrapper {
max-height: 45px !important;
}
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
With these changes in mind, you can now use useCallback so that those two functions are not re-created on each re-render, as they don't need to access selectedHomeName or selectedAwayName anymore:
const selectHomeTeamStat = React.useCallback(({ target }) => {
setSelectedHomeName(target.textContent);
}, []);
const selectAwayTeamStat = React.useCallback(({ target }) => {
setSelectedAwayName(target.textContent);
}, []);
setState is asynchronous. You are logging the state in the current render phase, the updated value will be available only on the next render.
To fix it, pass the current value item.name or use useEffect
const selectHomeTeamStat = (evt) => {
const { value } = evt.target;
setSelectedHomeOption(value);
}
useEffect(() => {
const item = items.find((item) => item.team_id == selectedHomeOption);
getStats(leagueId, selectedHomeOption, item.name);
}, [leagueId, items, selectedHomeOption]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With