Home > Mobile >  React useState with object: how to avoid the initial re-rendering, caused by the call to setState wi
React useState with object: how to avoid the initial re-rendering, caused by the call to setState wi

Time:11-17

I'm sure I'm missing something, but I can't find a clear anwser here. I'm trying to minimize re-renderings, at least avoid useless renderings like in this case.

  1. The state is an object, because I'd like to set both loading and products properties with a single setState call
  2. Initially, loading is true because I'm sure that useEffect is called at least once
  3. Calling setState inside useEffect will trigger an useless immediate re-render, because I think how object are compared (shallow vs deep)

Actual code:

export const ProductsIndex = () => {
  console.log('[ProductsIndex] Rendering');

  const [state, setState] = useState({ loading: true, products: [], pages: 1 });

  useEffect(() => {
    console.log('[ProductsIndex] useEffect');

    setState(prev => ({ ...prev, loading: true });
    axios.get('/api/products', params)
      .then(res => {
        setState(prev => ({ ...prev, loading: false, products: res.data });
      });

    // fetch
  }, [params]);
};

I can think at this solution, but I really don't know if there is a better way to handle this use cases:

useEffect(() => {
  console.log('[ProductsIndex] useEffect');

  setState(prev => prev.loading ? prev : { ...prev, loading: true });
}, []);

CodePudding user response:

It'd be simple enough to only set the state initially if it's different from the current state.

useEffect(() => {
    console.log('[ProductsIndex] useEffect');
    if (!state.loading) {
        setState(prev => ({ ...prev, loading: true });
    }

But setting state unnecessarily usually shouldn't be something you need to worry about. It's not a problem for React, and a well designed app usually shouldn't depend on the component not re-rendering at a certain point. If it causes problems for you, you can use various hooks to monitor and account for state/prop changes if needed, especially useMemo and the cleanup function returned by useEffect.

If a component happens to re-render twice when an app starts up, or even 5 or 10 times - it'll usually be completely imperceptible to the user, and so isn't worth worrying about. You just need to make sure that other logic in your app is also written with the possibility of re-renderings in mind.

CodePudding user response:

To be honest the way you are going about this seems to be wrong. You can probably rewrite this in a better manner using custom hooks.

import { useEffect, useState } from "react"
import axios from "axios"


export default function useFetch(url){

    const [data,setData] = useState(null)
    const [error,setError] = useState(null)
    const [loading,setLoading] = useState(false)

    useEffect(() => {
        (
            async function(){
                try{
                    setLoading(true)
                    const response = await axios.get(url)
                    setData(response.data)
                }catch(err){
                    setError(err)
                }finally{
                    setLoading(false)
                }
            }
        )()
    }, [url])

    return { data, error, loading }

}

Try something like this. Reference: https://dev.to/shaedrizwan/building-custom-hooks-in-react-to-fetch-data-4ig6

CodePudding user response:

The reason why your first setState triggers a re-render even though the state objects are identical, is because, as React official documentation explains, states use the Object.is() comparison algorithm. It is easy to see then what happens:

const obj1 = { loading: true, products: [], pages: 1 };
const obj2 = { ...obj1, loading: true};

Object.is(obj1, obj2); //false

The reason for this behaviour is that, simply, despite having the same values, obj1 and obj2 are different references of objects. So, as @CertainPerformance stated, why not simply control before setting the state?

Another approach might be the one suggested in this very useful question: Does react re-render the component if it receives the same value in state?

  • Related