Photo by Ferenc Almasi on Unsplash
Asynchronous Nature of React useState()
Don't try accessing state values immediately after setting the state.
Recently, I came to know that the useState() hook in react component is asynchronous in nature.
I was working on a react application that displays random quotes whenever a new quote button is clicked. Initially, I was unaware of the fact that useState() is asynchronous and got an undefined properties error.
Let's see how I fixed those errors in this article.
Here's my initial solution which was having an error.
import React, { useEffect, useState } from "react";
import axios from "axios";
import "./App.css";
export default function App() {
const [quotesArr, setQuotesArr] = useState([]);
const [currentQuote, setCurrentQuote] = useState({ text: "", author: "" });
useEffect(() => {
const getQuotesArr = async () => {
const { data } = await axios.get(
"https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotesArr.json"
);
setQuotesArr(data.quotes);
console.log(quotesArr); // this will log empty array to the console😒
generateRandomQuote();
};
getQuotesArr();
}, []);
const generateRandomQuote = () => {
console.log(quotesArr, quotesArr.length); //got output as [], 0 😑
const randomQuoteId = Math.floor(Math.random() * quotesArr.length);
setCurrentQuote(quotesArr[randomQuoteId]);
};
return (
<div id="quote-box">
<div id="text">{currentQuote.quote}</div>
<div id="author">-{currentQuote.author}</div>
<div id="links">
<a id="tweet-quote" target="_blank" href="twitter.com/intent/tweet">
<i className="fa-brands fa-twitter"></i>
</a>
<button id="new-quote" onClick={generateRandomQuote}>
New Quote
</button>
</div>
</div>
);
}
I was trying to set a quote from quotesArr state to currentQuote immediately after quotesArr state was initialized.
I created generateRandomQuote() for this purpose. I was expecting it to generate a random number to fetch a quote from quotesArr state. But unfortunately, quotesArr is coming as an empty array and got below the below error in the console.
I called generateRandomQuote() only after initializing quotesArr with API response. Then, how come its value became empty inside that function?
Later, I googled and found that useState() is asynchronous and we should not access state immediately after initializing.
To fix this issue, I moved generateRandomQuote() to the new useEffect which will execute whenever quotesArr state is modified.
And, Here's my final solution
import React, { useEffect, useState } from "react";
import axios from "axios";
import "./App.css";
export default function App() {
const [quotesArr, setQuotesArr] = useState([]);
const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });
useEffect(() => {
const getQuotesArr = async () => {
const { data } = await axios.get(
"https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json"
);
setQuotesArr(data.quotes);
};
getQuotesArr();
}, []);
useEffect(() => {
//Validation to return null if quotesArr is empty.
if (!quotesArr.length) {
return null;
}
generateRandomQuote();
}, [quotesArr]);
const generateRandomQuote = () => {
const randomQuoteId = Math.floor(Math.random() * quotesArr.length);
setCurrentQuote(quotesArr[randomQuoteId]);
};
return (
<div id="quote-box">
<div id="text">{currentQuote.quote}</div>
<div id="author">-</div>
<div id="links">
<a id="tweet-quote" target="_blank" href="twitter.com/intent/tweet">
<i className="fa-brands fa-twitter"></i>
</a>
<button id="new-quote" onClick={generateRandomQuote}>
New Quote
</button>
</div>
</div>
);
}
If you've reached this point, thank you very much. If you’ve found this article useful, please do consider telling your friends about it. I'll see you all in the next 😊