• [React] useEffect problem: dependency is Object


    Let's say we have a simple app look like this:

    import { useEffect, useState } from "react";
    
    function useFetch(config) {
      console.log("useFetch call");
      const [data, setData] = useState(null);
      useEffect(() => {
        if (config.url) {
          fetch(config.url)
            .then((response) => response.json())
            .then((json) => {
              setData(json);
            });
        }
      }, [config]);
    
      return { data };
    }
    
    export default function App() {
      const [url, setUrl] = useState(null);
      const { data } = useFetch({ url });
      return (
        <div className="App">
          <div>Hello</div>
          <div>{JSON.stringify(data)}</div>
          <div>
            <button onClick={() => setUrl("/jack.json")}>Jack</button>
            <button onClick={() => setUrl("/jelly.json")}>Sally</button>
          </div>
        </div>
      );
    }

    It has a useFetchcustome hook, when any button clicked, it will be invoked because urlchanges will trigger `useFetch` re-run.

      const [url, setUrl] = useState(null);
      const { data } = useFetch({ url });

    But the problem here is that, it get stuck in infinity loop:

    The reason is we passing {url}to useFetch, and inside useFetch, there is useEffect which deps on {url}.

    Solution 1: instead passing object, just pass primitve value:

    function useFetch(config) {
      console.log("useFetch call");
      const [data, setData] = useState(null);
      useEffect(() => {
        if (config.url) {
          fetch(config.url)
            .then((response) => response.json())
            .then((json) => {
              setData(json);
            });
        }
      }, [config.url]); // using config.url instead of config object
     
      return { data };
    }

    But what if we have also callback function inside config object?

    export default function App() {
      const [url, setUrl] = useState(null);
      const onSuccess = () => console.log("success");
      const { data } = useFetch({ url, callback: onSuccess });
        
        
    ...
    
    function useFetch(config) {
      console.log("useFetch call");
      const [data, setData] = useState(null);
      useEffect(() => {
        if (config.url) {
          fetch(config.url)
            .then((response) => response.json())
            .then((json) => {
              setData(json);
              config.callback();
            });
        }
      }, [config.url, config.callback]); // add callback as deps
    
      return { data };
    }

    Now again, it become crazy.

    Solution to the callback problem, we can use useRefto resolve it:

    function useFetch(config) {
      console.log("useFetch call");
      const [data, setData] = useState(null);
      const onSuccessRef = useRef(config.callback);
      useEffect(() => {
        if (config.url) {
          fetch(config.url)
            .then((response) => response.json())
            .then((json) => {
              setData(json);
              onSuccessRef.current?.();
            });
        }
      }, [config.url]);
    
      return { data };
    }

    This solution doesn't work if callback changed... so we need to do

    import { useEffect, useRef, useState, useLayoutEffect } from "react";
    
    function useFetch(config) {
      const [data, setData] = useState(null);
      const onSuccessRef = useRef(config.callback);
      useLayoutEffect(() => {
        onSuccessRef.current = config.callback
      }, [config.callback])
    
      useEffect(() => {
        if (config.url) {
          fetch(config.url)
            .then((response) => response.json())
            .then((json) => {
              setData(json);
              onSuccessRef.current?.();
            });
        }
      }, [config.url]);
    
      return { data };
    }

    We use useLayoutEffectto keep ref callback up to date.

    To improve, we can create a helper function:

    import { useEffect, useRef, useState, useLayoutEffect } from "react";
    
    function useCallbackRef(callback) {
      const callbackRef = useRef(callback);
      useLayoutEffect(() => {
        callbackRef.current = callback;
      }, [callback]);
      return callbackRef;
    }
    
    function useFetch(config) {
      const [data, setData] = useState(null);
      const savedOnSuccess = useCallbackRef(config.callback).current;
      useEffect(() => {
        if (config.url) {
          fetch(config.url)
            .then((response) => response.json())
            .then((json) => {
              setData(json);
              savedOnSuccess();
            });
        }
      }, [config.url]);
    
      return { data };
    }

    Add cancellation to the useEffect:

    import { useEffect, useRef, useState, useLayoutEffect } from "react";
    
    function useCallbackRef(callback) {
      const callbackRef = useRef(callback);
      useLayoutEffect(() => {
        callbackRef.current = callback;
      }, [callback]);
      return callbackRef;
    }
    
    function useFetch(config) {
      const [data, setData] = useState(null);
      const savedOnSuccess = useCallbackRef(config.callback).current;
      useEffect(() => {
        let isCancelled = false;
        if (config.url) {
          fetch(config.url)
            .then((response) => response.json())
            .then((json) => {
              if (!isCancelled) {
                setData(json);
                savedOnSuccess();
              }
            });
        }
    
        return () => {
          isCancelled = true;
        };
      }, [config.url]);
    
      return { data };
    }
  • 相关阅读:
    状态模式
    maven-war-plugin 插件 web.xml 缺失时忽略
    Java远程方法协议(JRMP)
    Java Singleton的3种实现方式
    浅谈分布式消息技术 Kafka
    浅谈分布式事务
    J2EE开发时的包命名规则,养成良好的开发习惯
    使用Dom4j创建xml文档
    Java HttpClient Basic Credential 认证
    Spring MVC的Post请求参数中文乱码的原因&处理
  • 原文地址:https://www.cnblogs.com/Answer1215/p/16829497.html
Copyright © 2020-2023  润新知