3

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.

1 Answer 1

6

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().

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. I changed the hook to memo a hash of the query object and passing that as the dependency to useEffect!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.