How to update react useState without delay?

Viewed 3557

When I call fetchProducts I want productPage state to be updated before it runs the next line of code since the next line requires the updated state.

The state only gets updated after the function has finished running. No matter where in the fetchProducts function I put console.log(productPage) it returns the state as it was before the function was called.

   const [productPage, setProductPage] = useState(1)
   const [displayProducts, setDisplayProducts] = useState([])

   const fetchProducts = async () => {

      setProductPage(productPage + 1) // DOES NOT UPDATE UNTIL AFTER FUNCTION RUNS

      const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
      const newProductsArr = await newProductsData.json()
      setDisplayProducts(displayProducts.concat(newProductsArr))

   }

Is this possible? I've found workarounds to the problem but it feels hacky and not ideal.

I'm using next.js also, I'm not sure if that would make a difference. I can't use normal variables as they reset on each render.

2 Answers

setProductPage will update the state and trigger a new render. productPage is not the state, it's just a variable holding the value of the state the moment you used useState. It will never change, it will always have the same value. You think it changes, but in reality, the function is simply executed again, but now productPage assigned with the new value.

So what can you do? You have two options.

The one it use useEffect, that will see if productPage changes (every time the function is executed = re-renders), and if it is, it will fetch the data and use setDisplayProducts to re-render the component with a new value in displayProducts. This is nice if you plan to have other ways of updating the productPage and you want them to also trigger the fetch.

const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])

const fetchProducts = async () => {
   setProductPage(productPage + 1);
}

useEffect(() => {
    const getDisplayProducts = async () => {
        const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
        const newProductsArr = await newProductsData.json()
        setDisplayProducts(displayProducts.concat(newProductsArr))
    };

    getDisplayProducts();
}, [productPage]);

The second one is just store the new value in a variable, pass the variable to the setProductPage but use the variable in the fetch and not productPage.

const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])

const fetchProducts = async () => {
    const nextProductPage = productPage + 1;

    setProductPage(nextProductPage);
    const newProductsData = await fetch(`/api/getproducts?page=${nextProductPage}`)
    const newProductsArr = await newProductsData.json()
    setDisplayProducts(displayProducts.concat(newProductsArr));
};

Setting a state is an async operation and you can't use it like a sync operation.

So, you must use useEffect hook, which runs after state changes.

import React, {useState, useEffect} from 'react';

const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])

const fetchProducts = async () => {
   setProductPage(productPage + 1)
}

useEffect(() => {
   const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
   const newProductsArr = await newProductsData.json()
   setDisplayProducts(displayProducts.concat(newProductsArr))
}, [productPage]); // <-- monitor productPage for changes
Related