简体   繁体   中英

Strange useEffect infinite loop

Seeing some weird things with a hook we use for dealing with apiCalls, where a sudden change in api response structure triggered infinite request loops in our app. I have slimmed it down to the bare requirements to reproduce the bug.

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

const mockedApiCall = (resolvedValue, time) =>
    new Promise((resolve, reject) => setTimeout(() => resolve(resolvedValue), time));

const useHook = (query) => {
    const [error, setError] = useState(null);
    const [data, setData] = useState([]);

    useEffect(() => {
        const asyncOperation = async () => {
            const response = await mockedApiCall([], 1000);

            // Testing something i know will fail:
            const mappedData = response.data.map(a => a);

            // if the above didn't fail we would update data
            setData(mappedData);
        };

        // Clear old errors
        setError(null);
        // trigger operation
        asyncOperation().catch(e => {
            console.log('fail');
            setError('Some error');
        });
    }, [query]);

    return [data, error];
}


const HookIssue = props => {
    const [data, error] = useHook({foo: 'bar'}); // Creates infinite loop
    //const [data, error] = useHook('foo') // only fails a single time

    // Alternative solution that also doesn't create infinite loop
    // const query = useMemo(() => ({foo: 'bar'}), []);
    // const [data, error] = useHook(query);


    return null;
};

export default HookIssue;

Does anyone know what is happening here? The hook is supposed to only listen to changes in the query variable, but in some cases will just run into an infinite loop.

Edit: To add some more weirdness to this issue, if i remove either (or both) of the two setError calls inside the useEffect it would also prevent the infinite loop.

You're getting this loop because you're passing an object to useHook() . Objects are reference types - that is, they aren't defined by their value. The following statement evaluates to false for this reason:

{ foo: "bar" } === { foo: "bar" }

Every time HookIssue is rendered, it will create a new object of { foo: "bar" } . This is passed to useEffect() in the dependency array, but because objects are reference types - and each object on each render will have a different reference - React will always "see" the query value as having changed.

This means that every time asyncOperation finishes, it will cause a re-render of HookIssue , which will cause another effect to be triggered.

To fix this, you need to pass a value type or a string to the dependency array instead, or you need to "stabilize" the query object through the use of useMemo() .

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