I recently asked a Stack Overflow post about useState()
taking 2 clicks. I was told I could immediately update a boolean state by doing setState(state => !state)
. However, this only works for booleans. Take this example:
let [popularQuotes, setPopularQuotes] = useState([])
let [newQuotes, setNewQuotes] = useState([])
let [selected, select] = useState("popular")
const getQuotes = async() => {
Post('https://example.com/api/quotes', {
sort: selected
}).then((r) => selected == "popular" ? setPopularQuotes(r) : setNewQuotes(r))
}
When I want to toggle from popular quotes to new quotes, such as like this onClick={() => {select("popular"); getQuotes()}}
, it takes 2 clicks, as the state newQuotes
remains initially unchanged for the first click (just being an empty array). How can I counteract this and update the state on the first click?
CodePudding user response:
Add an effect hook like this to your function component body for triggering the Api whenever selected
is changed:
useEffect(() => {
getQuotes(selected);
},[selected]);
Change getQuotes
to get selected
value as a parameter:
const getQuotes = async(selectedItem) => {
Post('https://example.com/api/quotes', {
sort: selectedItem
}).then((r) => selectedItem == "popular" ? setPopularQuotes(r) : setNewQuotes(r))
}
Finally modify onClick
callback like this:
onClick={() => select("popular")}
CodePudding user response:
You are running into the fact that setting state is async, therefore -
onClick={() => {select("popular"); getQuotes()}}
will not be calling getQuotes
with the updated value of 'selected' being 'popular'
I suggest you either handle the currently required quotes in state, and let an effect take care of the API call, however be careful if your components aren't well structure and pure or you could end up with more API calls than want you wish for -
const [popularQuotes, setPopularQuotes] = useState([])
const [newQuotes, setNewQuotes] = useState([])
const [selected, select] = useState("popular")
const getQuotes = async(selected) => {
Post('https://example.com/api/quotes', {
sort: selected
}).then((r) =>
selected === "popular" ?
setPopularQuotes(r) : setNewQuotes(r))
)
}
useEffect(() => {
getQuotes(selected);
}, [getQuotes, selected])
...
or pass the quote type to getQuotes
as an argument, and keep track of the previously requested quote type in state -
let [selected, select] = useState("popular")
...
onClick={() => {
getQuotes(selected === 'popular' ? 'new' : 'popular');
select("popular");
}}
though this gets hard to maintain as your state needs to clearly define if its storing the state of the next to call, or the current thats been displayed, and therefore the getQuotes
needs to act on the opposite
CodePudding user response:
As you mentioned in your question, you will need to update the state using a callback.
In the below example, i'm using the previous state value but it's not mandatory.
const [someState, setSomeState] = useState({id: 'idA', name: 'someNameA'})
setSomeState(prevState => ({id: prevState.id 'B', name: prevState.name 'B'}));
More info can be found here: The useState set method is not reflecting a change immediately