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);
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.
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.
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.
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.
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
---
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
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With