heliosk
heliosk

Reputation: 1159

React infinite loop on change state in useEffect()

I'm trying to update a state inside a useEffect, I'm getting:

React Hook useEffect has a missing dependency: 'user'. Either include it or remove the dependency array. You can also do a functional update 'setUser(u => ...)' if you only need 'user' in the 'setUser' call react-hooks/exhaustive-deps

If I put user state as a dependency then I get an infinite loop.

    const [user, setUser] = useState({
        displayName: '',
        hasPassword: false,
        ...
    });

    let { userId } = useParams();
    
    useEffect(() => {
    
        const checkPassword = async (userId) => {

            try {
                const res = await UserService.checkPassword(userId);

                if (res.status === 200) {
                    setUser({...user, hasPassword: res.data.hasPassword });
                }

            } catch (err) {
                setUser({...user, hasPassword: false });
            }
        };

        checkPassword(userId);
        
    }, [userId, user]);

If I remove user from dependency array than everything works fine, but I'm trying to get rid of that warning. I just need to execute this function only once.

Upvotes: 0

Views: 176

Answers (4)

pritam
pritam

Reputation: 2558

Since you have circular dependancy i.e.

  • You effect runs when user object changes
  • In the effect, you update your user object again

Note that, reference of your user object changes on every setUser call, that's why there's infinite loop.

I wouldn't recommend having user in your dependancy array as you could update user details if userId changes.

If still you can't remove user dependancy, you could use JSON.stringify(user) as dependancy instead as the effect would run only when stringified representation of your user object changes.


 useEffect(() => {
    // your effect code
 }, [userId, JSON.stringify(user)]);

Note: Use stringify() only if you don't have values with undefined/NaN etc as the keys would be lost after conversion.

Upvotes: -1

norbitrial
norbitrial

Reputation: 15176

I guess you can use the previous state of the user instead of the state user as:

setUser(prevState => ({
   ...prevState,
   hasPassword: res.data.hasPassword
}));

Thus you don't need user in the dependency array.

Final code would be with the suggestion:

useEffect(() => {
    const checkPassword = async (userId) => {
       try {
          const res = await UserService.checkPassword(userId);

          if (res.status === 200) {
             setUser(prevState => ({
                ...prevState,
                hasPassword: res.data.hasPassword
             }));
          }
       } catch (err) {
          setUser(prevState => ({
             ...prevState,
             hasPassword: false
          }));
       }
    };

    checkPassword(userId);    
}, [userId]);

Upvotes: 1

hackape
hackape

Reputation: 20007

You can use the setState callback syntax:

setUser(user => ({ ...user, hasPassword: whatever }))

Upvotes: 0

Beto Frega
Beto Frega

Reputation: 976

The method returned on the second position from useState also accepts a callback, so your code could be rewritten as this:

const [user, setUser] = useState({
        displayName: '',
        hasPassword: false,
        ...
    });

    let { userId } = useParams();
    
    useEffect(() => {
    
        const checkPassword = async (userId) => {

            try {
                const res = await UserService.checkPassword(userId);

                if (res.status === 200) {
                    setUser(user=>({...user, hasPassword: res.data.hasPassword }));
                }

            } catch (err) {
                setUser(user=>({...user, hasPassword: false }));
            }
        };

        checkPassword(userId);
        
    }, [userId]);

Upvotes: 1

Related Questions