Saseek Azmi
SaseekAzmi.com

SaseekAzmi.com

Asynchronous Nature of React useState()

Photo by Ferenc Almasi on Unsplash

Asynchronous Nature of React useState()

Don't try accessing state values immediately after setting the state.

Saseek Azmi's photo
Saseek Azmi
·Feb 19, 2022·

3 min read

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.

image.png

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 😊

 
Share this