如何在 useEffect 中跟踪对象引用?

huangapple go评论61阅读模式
英文:

How to keep track of the object reference in a useEffect?

问题

I'm building some API hooks and I have trouble with deps property. I want to reinvoke the useEffect when content of deps changes, instead I'm running in a forever loop. deps can be either undefined or an any[].

export const useReader = <TResponseData>({ url, deps, options }: TypeUseReader<TResponseData>) => {
    const [data, setData] = useState<TResponseData | null>(null);
    const [isLoading, setIsLoading] = useState<boolean>(false);
    const [error, setError] = useState<number | null >(null);

    async function fetchData(newUrl?: string) {}

    useEffect(() => {
       // ..fetch
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [url, deps]); // HERE, deps are forever loop

    return {
        data,
        isLoading,
        error,
        fetchData
    };
};

That's how I'm using it:

 return useReader<FOO>(
        {
            url: 'URL_PLACEHOLDER',
            deps: [anything] /* FIXME: deps enters FOREVER LOOP */
        }
    );

What I have tried:

[deps ?? []] and ...[deps ?? []] and deps - all of these create forever loop

英文:

I'm building some API hooks and I have trouble with deps property. I want to reinvoke the useEffect when content of deps changes, instead I'm running in a forever loop. deps can be either undefined or an any[]

export const useReader = &lt;TResponseData&gt;({ url, deps, options }: TypeUseReader&lt;TResponseData&gt;) =&gt; {
    const [data, setData] = useState&lt;TResponseData | null&gt;(null);
    const [isLoading, setIsLoading] = useState&lt;boolean&gt;(false);
    const [error, setError] = useState&lt;number | null &gt;(null);

    async function fetchData(newUrl?: string) {}

    useEffect(() =&gt; {
       // ..fetch
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [url, deps]); // HERE, deps are forever loop

    return {
        data,
        isLoading,
        error,
        fetchData
    };
};

That's how I'm using it:

 return useReader&lt;FOO&gt;(
        {
            url: &#39;URL_PLACEHOLDER&#39;,
            deps: [anything] /* FIXME: deps enters FOREVER LOOP */
        }
    );

What I have tried:

[deps ?? []] and ...[deps ?? []] and deps - all of these create forever loop

答案1

得分: 0

The problem is that when you're calling useReader with deps as an array literal, a new array is being created each time, so useEffect rightly detects a change in the value.

Broadly speaking, there are two ways to deal with this.

You can make it the responsibility of the caller – specify in the useReader interface that it accepts a single dep value of unknown type. If the caller wants to pass through an array of values, then these should be stabilized, e.g.

const dep = useMemo(() => [val1, val2], [val1, val2]);
return useReader<FOO>(
        {
            url: 'URL_PLACEHOLDER',
            isSuspended: false,
            initialValue: foo,
            dep   /* only changes if val1 or val2 has changed */
        }
    );

Easier and more flexible is to accept an array of deps (typed as unknown[]), and just spread the members of the array after the other dependencies you're passing to useEffect:

useEffect(() => {
        if (!isSuspended) {
            fetchData();
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isSuspended, url, ...(deps ?? [])]);

The various linters do get upset about this, but it's not problematic as far as the hook machinery is concerned.

英文:

The problem is that when you're calling useReader with deps as an array literal, a new array is being created each time, so useEffect rightly detects a change in the value.

Broadly speaking, there are two ways to deal with this.

You can make it the responsibility of the caller – specify in the useReader interface that it accepts a single dep value of unknown type. If the caller wants to pass through an array of values, then these should be stabilized, e.g.

const dep = useMemo(() =&gt; [val1, val2], [val1, val2]);
return useReader&lt;FOO&gt;(
        {
            url: &#39;URL_PLACEHOLDER&#39;,
            isSuspended: false,
            initialValue: foo,
            dep   /* only changes if val1 or val2 has changed */
        }
    );

Easier and more flexible is to accept an array of deps (typed as unknown[]), and just spread the members of the array after the other dependencies you're passing to useEffect:

    useEffect(() =&gt; {
        if (!isSuspended) {
            fetchData();
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isSuspended, url, ...(deps ?? [])]);

The various linters do get upset about this, but it's not problematic as far as the hook machinery is concerned.

huangapple
  • 本文由 发表于 2023年4月17日 05:03:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76030319.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定