Search code examples
reactjsreact-hooksuse-effect

Custom hook and dynamic useEffect dependency


I'm using this useFetch custom hook in multiple components.


import {useState,useEffect} from 'react'
import {getToken} from './../services/token'

const useFetch = (url) => {

    const [data,setData]       = useState(null);
    const [error,setError]     = useState(null);
    const [loading,setPending] = useState(false);

    useEffect(() => {

        (async()=>{

          const abortCont = new AbortController();
          const token     = await getToken();

          try {

            setPending(true);

            const response = await fetch(url,{
              signal  : abortCont.signal,

              headers : {
                "Content-Type"  : "application/json",
                "Authorization" : "Bearer "+token
              }

            });

            const result    = await response.json();

            setData(result);
            setPending(false);

          } catch (err) {

            if(err.name === 'AbortError'){
                abortCont.abort();
                return;
            }

            setError(String(err));
            setPending(false);

          }

        })()

      }, [])

    return {data,error,loading}
}

export default useFetch;



And I import it in a users components as below.


const {data,error,loading} = useFetch('http://localhost:4040/users');

Normally I pass the second parameter of UseEffect like [users.length] to make the users data reactive but as this is a custom hook and being used in many components, How can I make the users data to be reactive when users data was changed or updated?

Codesandbox Link


Solution

  • You can pass the dependency array as the optional parameter to your custom hook.

    const useFetch = async (url, deps) => {
    
    const [data,setData]       = useState(null);
    const [error,setError]     = useState(null);
    const [loading,setPending] = useState(false);
    
    useEffect(() => {
    
        (async()=>{
    
          const abortCont = new AbortController();
          const token     = await getToken();
    
          try {
    
            setPending(true);
    
            const response = await fetch(url,{
              signal  : abortCont.signal,
    
              headers : {
                "Content-Type"  : "application/json",
                "Authorization" : "Bearer "+token
              }
    
            });
    
            const result    = await response.json();
    
            setData(result);
            setPending(false);
    
          } catch (err) {
    
            if(err.name === 'AbortError'){
                abortCont.abort();
                return;
            }
    
            setError(String(err));
            setPending(false);
    
          }
    
        })()
    
      }, deps || [])
    
        return {data,error,loading}
    }
    
    export default useFetch;
    

    Then call it like

    const {data,error,loading} = useFetch('http://localhost:4040/users', [users.length]);
    

    EDIT

    In your case you want to have some more control over the rendering. So i'll say you have to modify your hook to support setting data for your mounted component.

    const useFetch = async (url, holded, setHolded) => {
    
    const [data,setData]       = useState(null);
    const [error,setError]     = useState(null);
    const [loading,setPending] = useState(false);
    
    useEffect(() => {
    
        (async()=>{
    
          const abortCont = new AbortController();
          const token     = await getToken();
    
          try {
    
            setPending(true);
    
            const response = await fetch(url,{
              signal  : abortCont.signal,
    
              headers : {
                "Content-Type"  : "application/json",
                "Authorization" : "Bearer "+token
              }
    
            });
    
            const result    = await response.json();
    
            setData(result);
            if(setHolded) {
                setHolded(result);
            }
            setPending(false);
    
          } catch (err) {
    
            if(err.name === 'AbortError'){
                abortCont.abort();
                return;
            }
    
            setError(String(err));
            setPending(false);
    
          }
    
        })()
    
      }, (holded && setHolded && [holded,setHolded,url]) || [url])
    
        return {data,error,loading}
    }
    
    export default useFetch;
    

    Then call it like

    const {data,error,loading} = useFetch('http://localhost:4040/users', users, setUsers);