简体   繁体   中英

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.

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]);

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]);

您可以使用 setState 回调语法:

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

Since you have circular dependancy ie

  • 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM