Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Hook useEffect has a missing dependency: 'dispatch'

This is my first time working with react js , im trying to remove the alert when leaving this view cause i don't want to show it on the other view but in case that there is no error i want to keep the success alert to show it when i'm gonna redirect to the other view

but im getting this wearning on google chrome Line 97:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps

if i did include dispatch i get infinite loop

const [state, dispatch] = useUserStore(); useEffect(() => {     let token = params.params.token;     checktoken(token, dispatch);   }, [params.params.token]);    useEffect(() => {     return () => {       if (state.alert.msg === "Error") {         dispatch({           type: REMOVE_ALERT         });       }     };   }, [state.alert.msg]);  //response from the api if (!token_valide || token_valide_message === "done") {       return <Redirect to="/login" />;     } 

this is useUserStore

  const globalReducers = useCombinedReducers({     alert: useReducer(alertReducer, alertInitState),     auth: useReducer(authReducer, authInitState),     register: useReducer(registerReducer, registerInitState),     token: useReducer(passeditReducer, tokenvalidationInitState)   });   return (     <appStore.Provider value={globalReducers}>{children}</appStore.Provider>   ); };  export const useUserStore = () => useContext(appStore); 
like image 320
Buk Lau Avatar asked Oct 30 '19 11:10

Buk Lau


People also ask

How do I fix React hook useEffect has missing dependencies?

The warning "React Hook useEffect has a missing dependency" occurs when the useEffect hook makes use of a variable or function that we haven't included in its dependencies array. To solve the error, disable the rule for a line or move the variable inside the useEffect hook.

Can I dispatch action in useEffect?

Note: React guarantees that dispatch function identity is stable and won't change on re-renders. This is why it's safe to omit from the useEffect or useCallback dependency list. You would need to pass either bound action creators or a reference to dispatch to your hook.

What is a missing dependency?

Missing dependencies are those dependencies that are not available in the repository, so you cannot add them to your deployment set. You can set Deployer to ignore missing dependencies when you create the project (see Creating a Project) or when you check unresolved dependencies.

What is useEffect dependency?

useEffect(callback, dependencies) is the hook that manages the side-effects in functional components. callback argument is a function to put the side-effect logic. dependencies is a list of dependencies of your side-effect: being props or state values.


2 Answers

UPDATE 09/11/2020

This solution is no longer needed on [email protected] and above.

Now useMemo and useCallback can safely receive referential types as dependencies.#19590

function MyComponent() {   const foo = ['a', 'b', 'c']; // <== This array is reconstructed each render   const normalizedFoo = useMemo(() => foo.map(expensiveMapper), [foo]);   return <OtherComponent foo={normalizedFoo} /> } 

Here is another example of how to safely stabilize(normalize) a callback

const Parent = () => {     const [message, setMessage] = useState('Greetings!')      return (         <h3>             { message }         </h3>         <Child setter={setMessage} />     ) }  const Child = ({     setter }) => {     const stableSetter = useCallback(args => {         console.log('Only firing on mount!')         return setter(args)     }, [setter])      useEffect(() => {         stableSetter('Greetings from child\'s mount cycle')     }, [stableSetter]) //now shut up eslint      const [count, setCount] = useState(0)      const add = () => setCount(c => c + 1)      return (         <button onClick={add}>             Rerender {count}         </button>     ) } 

Now referential types with stable signature such as those provenients from useState or useDispatch can safely be used inside an effect without triggering exhaustive-deps even when coming from props

Edit silly-andras-9v1yp

---

Old answer

dispatch comes from a custom hook so it doesn't have an stable signature therefore will change on each render (reference equality). Add an aditional layer of dependencies by wrapping the handler inside an useCallback hook

   const [foo, dispatch] = myCustomHook()       const stableDispatch = useCallback(dispatch, []) //assuming that it doesn't need to change     useEffect(() =>{         stableDispatch(foo)    },[stableDispatch]) 

useCallback and useMemo are helper hooks with the main purpose off adding an extra layer of dependency check to ensure synchronicity. Usually you want to work with useCallback to ensure a stable signature to a prop that you know how will change and React doesn't.

A function(reference type) passed via props for example

const Component = ({ setParentState }) =>{     useEffect(() => setParentState('mounted'), []) } 

Lets assume you have a child component which uppon mounting must set some state in the parent (not usual), the above code will generate a warning of undeclared dependency in useEffect, so let's declare setParentState as a dependency to be checked by React

const Component = ({ setParentState }) =>{     useEffect(() => setParentState('mounted'), [setParentState]) } 

Now this effect runs on each render, not only on mounting, but on each update. This happens because setParentState is a function which is recreated every time the function Component gets called. You know that setParentState won't change it's signature overtime so it's safe to tell React that. By wrapping the original helper inside an useCallback you're doing exactly that (adding another dependency check layer).

const Component = ({ setParentState }) =>{    const stableSetter = useCallback(() => setParentState(), [])     useEffect(() => setParentState('mounted'), [stableSetter]) } 

There you go. Now React knows that stableSetter won't change it's signature inside the lifecycle therefore the effect do not need too run unecessarily.

On a side note useCallback it's also used like useMemo, to optmize expensive function calls (memoization).

The two mai/n purposes of useCallback are

  • Optimize child components that rely on reference equality to prevent unnecessary renders. Font

  • Memoize expensive calculations

like image 124
Dupocas Avatar answered Sep 21 '22 09:09

Dupocas


I think you can solve the problem at the root but that means changing useCombinedReducers, I forked the repo and created a pull request because I don't think useCombinedReducers should return a new reference for dispatch every time you call it.

function memoize(fn) {   let lastResult,     //initial last arguments is not going to be the same     //  as anything you will pass to the function the first time     lastArguments = [{}];   return (...currentArgs) => {     //returning memoized function     //check if currently passed arguments are the same as     //  arguments passed last time     const sameArgs =       currentArgs.length === lastArguments.length &&       lastArguments.reduce(         (result, lastArg, index) =>           result && Object.is(lastArg, currentArgs[index]),         true,       );     if (sameArgs) {       //current arguments are same as last so just       //  return the last result and don't execute function       return lastResult;     }     //current arguments are not the same as last time     //  or function called for the first time, execute the     //  function and set last result     lastResult = fn.apply(null, currentArgs);     //set last args to current args     lastArguments = currentArgs;     //return result     return lastResult;   }; }  const createDispatch = memoize((...dispatchers) => action =>   dispatchers.forEach(fn => fn(action)), ); const createState = memoize(combinedReducers =>   Object.keys(combinedReducers).reduce(     (acc, key) => ({ ...acc, [key]: combinedReducers[key][0] }),     {},   ), ); const useCombinedReducers = combinedReducers => {   // Global State   const state = createState(combinedReducers);    const dispatchers = Object.values(combinedReducers).map(     ([, dispatch]) => dispatch,   );    // Global Dispatch Function   const dispatch = createDispatch(...dispatchers);    return [state, dispatch]; };  export default useCombinedReducers; 

Here is a working example:

const reduceA = (state, { type }) =>    type === 'a' ? { count: state.count + 1 } : state;  const reduceC = (state, { type }) =>    type === 'c' ? { count: state.count + 1 } : state;  const state = { count: 1 };  function App() {    const [a, b] = React.useReducer(reduceA, state);    const [c, d] = React.useReducer(reduceC, state);    //memoize what is passed to useCombineReducers    const obj = React.useMemo(      () => ({ a: [a, b], c: [c, d] }),      [a, b, c, d]    );    //does not do anything with reduced state    const [, reRender] = React.useState();    const [s, dispatch] = useCombinedReducers(obj);    const rendered = React.useRef(0);    const [sc, setSc] = React.useState(0);    const [dc, setDc] = React.useState(0);    rendered.current++;//display how many times this is rendered    React.useEffect(() => {//how many times state changed      setSc(x => x + 1);    }, [s]);    React.useEffect(() => {//how many times dispatch changed      setDc(x => x + 1);    }, [dispatch]);    return (      <div>        <div>rendered {rendered.current} times</div>        <div>state changed {sc} times</div>        <div>dispatch changed {dc} times</div>        <button type="button" onClick={() => reRender({})}>          re render        </button>        <button          type="button"          onClick={() => dispatch({ type: 'a' })}        >          change a        </button>        <button          type="button"          onClick={() => dispatch({ type: 'c' })}        >          change c        </button>        <pre>{JSON.stringify(s, undefined, 2)}</pre>      </div>    );  }    function memoize(fn) {    let lastResult,      //initial last arguments is not going to be the same      //  as anything you will pass to the function the first time      lastArguments = [{}];    return (...currentArgs) => {      //returning memoized function      //check if currently passed arguments are the same as      //  arguments passed last time      const sameArgs =        currentArgs.length === lastArguments.length &&        lastArguments.reduce(          (result, lastArg, index) =>            result && Object.is(lastArg, currentArgs[index]),          true        );      if (sameArgs) {        //current arguments are same as last so just        //  return the last result and don't execute function        return lastResult;      }      //current arguments are not the same as last time      //  or function called for the first time, execute the      //  function and set last result      lastResult = fn.apply(null, currentArgs);      //set last args to current args      lastArguments = currentArgs;      //return result      return lastResult;    };  }    const createDispatch = memoize((...dispatchers) => action =>    dispatchers.forEach(fn => fn(action))  );  const createState = memoize(combinedReducers =>    Object.keys(combinedReducers).reduce(      (acc, key) => ({        ...acc,        [key]: combinedReducers[key][0],      }),      {}    )  );  const useCombinedReducers = combinedReducers => {    // Global State    const state = createState(combinedReducers);      const dispatchers = Object.values(combinedReducers).map(      ([, dispatch]) => dispatch    );      // Global Dispatch Function    const dispatch = createDispatch(...dispatchers);      return [state, dispatch];  };    ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>  <div id="root"></div>
like image 29
HMR Avatar answered Sep 20 '22 09:09

HMR