Eslint error on using callback hooks in React - reactjs

I am using callback hooks to stop the rendering of components that change due to a change of function passed as props, in every render. Here, I have added callback Hooks to incrementSalary and incrementAge. Callback hooks in this case seem to work fine. However eslint throws the following error:
Line 10:76: React Hook useCallback has an unnecessary dependency: 'age'. Either exclude it or remove the dependency array react-hooks/exhaustive-deps
Line 15:5: React Hook useCallback has an unnecessary dependency: 'salary'. Either exclude it or remove the dependency array react-hooks/exhaustive-dep
If I do not include callback hook, and I click on any of the button. It renders all other buttons in the parent component. I believe this happens because my functions are not memoized and using callback hooks and adding dependencies makes my functions memoized. So only the specific button which s clicked is rendered. I do not understand why eslint is throwing errors at me, is it due to the wrong use of callback or due to the wrong way of using prevState?
import React, { useState, useCallback } from "react";
import Title from "./Title";
import Count from "./Count";
import Button from "./Button";
function ParentComponent() {
const [age, setAge] = useState(22);
const [salary, setSalary] = useState(25000);
const incrementAge = useCallback(() => setAge((prevAge) => prevAge + 1), [
age,
]);
const incrementSalary = useCallback(
() => setSalary((prevSalary) => prevSalary + 5000),
[salary]
);
return (
<div>
<Title>Use Callback Hook</Title>
<Count text="Age" count={age} />
<Button handleClick={incrementAge}>Increment Age</Button>
<Count text="Salary" count={salary} />
<Button handleClick={incrementSalary}>Increment Salary</Button>
</div>
);
}
export default ParentComponent;

This is a warning/error that is coming for useCallback hook.
You don't need to add age and salary as dependencies in the useCallback hook. This is because there is no actual dependency on these states inside the callback.
const incrementAge = useCallback(
() => setAge((prevAge) => prevAge + 1),
[]); // Remove dependency.
const incrementSalary = useCallback(
() => setSalary((prevSalary) => prevSalary + 5000),
[]); // Remove dependency.

Related

Use set function from custom hooks inside useEffect

I created a custom hook that maintain state in a certain type based on data in a different type like this:
import { useState, useRef, Dispatch } from 'react';
function useBackedState<TData, TBacking>(initialData: TBacking, converter: (backing: TBacking) => TData):
[
state: TData,
getBackingData: () => TBacking,
setData: Dispatch<TBacking>
] {
const ref = useRef(initialData);
const [state, setState] = useState(converter(initialData));
const setData: Dispatch<TBacking> = (val: TBacking) => {
ref.current = val;
setState(converter(val));
};
return [
state,
() => ref.current,
setData
];
}
export default useBackedState;
However, I have problems when I call on setData inside a useEffect hook.
For example, I tested it in this component:
import { useState } from 'react';
import useBackedState from './hooks/useBackedState';
function Component() {
const [data, backing, setData] = useBackedState<string, string[]>(
[],
(arr) => arr.join('');
);
const [somethingElse, setSomethingElse] = useState('a');
useEffect(() => {
setSomethingElse('b');
setData(['h', 'e', 'l', 'l', 'o']);
}, []);
return (
<div>
<p>{data}</p>
... some more rendering
</div>
);
}
I want useEffect to only run once at the start, so I provided an empty array of dependencies. However, although the app works, I get this warning:
React Hook useEffect has a missing dependency: 'setData'. Either
include it or remove the dependency array
If I do add setData as a dependency, the entire app goes into an infinite loop of refresh and I get this error:
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
I am not required to add the setSomethingElse as a dependency to useEffect. Why am I required to add setData?
All state and prop values that get referenced inside a hook should have that value in the dependency array - so the argument goes. If you agree and wish to satisfy the linter, here, it's easy - just memoize the functions returned from the custom hook (which is often a good practice anyway, especially when exhaustive-deps is being used).
const setData: Dispatch<TBacking> = useCallback((val: TBacking) => {
ref.current = val;
setState(converter(val));
}, [setState, converter]);
I am not required to add the setSomethingElse as a dependency to useEffect
The linter can see that setSomethingElse comes directly from a state setter function declared in the same component, and thus definitely won't ever change, and so doesn't need to be in the dependency array.

Warning 'React hook useEffect has a missing dependency'

In a React app I get this warning on a few components in the useEffect function. I have seen other SO questions but still cant see a fix.
React Hook useEffect has a missing dependency: 'digiti' and 'time'. Either include it or remove the dependency array
const GenerateNumber = (props) => {
const [number, setNumber] = useState(0);
// add side effect to component
useEffect(() => {
const interval = setInterval(
() => setNumber(Math.floor(Math.random() * 9 + 1)),
50
);
setTimeout(() => { clearInterval(interval); setNumber(props.digiti); }, props.times * 100);
}, []);
return (
<span className='digit'>{number}</span>
);
}
This is something the react hooks exhaustive deps explain. In general, your dependency array should include all values used in the dependency array, however when you do this with something like setNumber, your useEffect hook will run infinitely as each change of setNumber triggers a new render (and each new render triggers setNumber, see the problem there?).
Your actual error, with the prop values of both digiti and times aim at you adding these two values to the dependency array, which would case the useEffect hook to run again every time these props change. It is up to you if this is intended behavior.
What is actually documented in the dependency array documentation is that it is intended behavior to leave the array empty to have the useEffect hook run exactly once.

Does setState function of useState hook trigger re-render of whole component or just the returned JSX part of the code?

I am a beginner in React and I am learning it from a udemy course. Initially I thought the whole code inside the functional component gets re rendered/re run after state update. But then I saw this implementation of countdown before redirect and got confused.
import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
const LoadingToRedirect = () => {
const [count, setCount] = useState(5);
let history = useHistory();
useEffect(() => {
const interval = setInterval(() => {
setCount((currentCount) => --currentCount);
}, 1000);
// redirect once count is equal to 0
count === 0 && history.push("/");
// cleanup
return () => clearInterval(interval);
}, [count]);
return (
<div className="container p-5 text-center">
<p>Redirecting you in {count} seconds</p>
</div>
);
};
export default LoadingToRedirect;
So why is setInterval needed here if setCount triggers a re-render of the whole code? Can't we do the same thing with setTimeout? So I tried that and it worked. Surely there is a reason he did it that way? Am I missing something?
Of course React re-renders the whole component but it also depends on some conditions. For example if you look at your code you have passed count variable as a dependency to useEffect hook, it means if the value of count changes React will render the effect inside of the useEffect hook.
Yes, you can achieve the same using setTimeout;setInterval is
pointless because it totally depends on count variable you passed as a
dependency.
if you remove count as a dependency then you can easily see it will
not redirect you the required page.

useEffect hook - dependencies - re-render issues

The first case:
_Let's say I have a prop that is in redux state or in parent state.
_I do not want the useEffect to fire whenever this prop changes,
_But I do need to use the prop within the useEffect.
_React warns me to add the prop to the dependency array, but if I do so, then the useEffect will fire again.
The second case:
_I am using a function within a useEffect,
_But the function is also needed elsewhere.
_Don't want to duplicate the function code.
_React wants me to add the function to the dependency array, but I don't want the useEffect to fire every time that function reference changes.
While working with useEffect you should think about closures. If any props or local variable is used inside the useEffect then it is advised to include it inside your dependency array else you will be working with the stale data due to the closure.
Here I present a use case. Inside ComponentUsingRef we are using ref which works as the container. You can find more about it at https://reactjs.org/docs/hooks-reference.html#useref
Advantage of this approach is that you wont be bound to memoize fn in
your parent component. You will always be using latest value of
function and in the given case it won't even cause firing of useEffect as it won't be on your dependency
const Component=({fn})=>{
useEffect(()=>{
fn()
},[fn])
.....
.....
return <SomeComponent {...newProps}/>
}
const ComponentUsingRef=({fn}){
const fnRef = useRef(fn)
fnRef.current = fn // so that our ref container contains the latest value
useEffect(()=>{
fn.current()
},[ ])
.....
.....
return <SomeComponent {...newProps}/>
}
If you want to use abstraction then you should extract your logic inside the custom hook
If the only issue is the warning then don't worry about it, you can simply disable the rule for your effect and omit the prop from the dependency array if it's the only prop being used in the effect. For effects with multiple dependencies, you can use useRef to store the latest value of the prop without firing the effect, like this:
const Comp = ({someProp, ...props}) => {
const myProp = useRef(someProp);
myProp.current = someProp;
// this effect won't fire when someProp is changed
React.useEffect(() => {
doSomething(myProp.current, props.a, props.b);
}, [props.a, props.b, myProp]);
}
For your second case, I'd probably put that function in a separate file and import it in the components where it'll be used, however, you can use the same pattern for your second case.
To ignore the warnings react gives you, you can disable them by adding // eslint-disable-next-line react-hooks/exhaustive-deps to the useEffect code. You can read more about this in the Rules of Hooks section in the React documentation. These rules are included in the eslint-plugin-react-hooks package.
To prevent the useEffect from firing everytime the function reference changes, you can use useCallback. The useCallback hook will store the reference to the function instead of the function itself. The reference of the function will only be updated when one of the dependencies of the function is updated. If you don't want the function reference to be updated ever, you can leave the dependency array empty in the same way as the dependency array of the useEffect hook.
Here is a working example of both:
import React, { useState, useEffect, useCallback } from "react";
import "./styles.css";
export default function App() {
const [value, setValue] = useState(0);
const wrappedFunction = useCallback(
(caller) => {
const newValue = value + 1;
setValue(newValue);
console.log(`Firing function from ${caller}: ${newValue}`);
},
[value]
);
useEffect(() => {
console.log(`Logging from useEffect: ${value}`);
wrappedFunction("useEffect");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<div>{value}</div>
<button onClick={() => wrappedFunction("button click")}>
Fire function
</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
Refer this link for the First Problem Sandbox
Here we are just using a state variable to hold the initial value, whether its coming from the Parent or Store, so Even if the value change inside the Parent or store, the child will still be on the older value, until unless it updates it using useEffect.
Parent
export default function App() {
const [state, setState] = useState("monika");
return (
<div className="App">
<h1>My Name is {state}</h1>
<button
onClick={() => {
setState("Rohan");
}}
>
change name
</button>
<First username={state} />
</div>
);
}
Child
export default function First({ username }) {
const [name, setName] = useState(username);
return (
<div>
<h1>My Name is {name}</h1>
</div>
);
}

Missing dependency: 'dispatch' in React

In my react/redux app, i'm using dispatch to call the action that retrieve data from state in redux each time the component is mounted. The problem is happening on useState My way does not work
Below is the error I'm getting:
React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array. Outer scope values like 'getInvoiceData' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
Here is my code:
const TableSection = () => {
const invoiceData = useSelector((state => state.tables.invoiceData));
const dispatch = useDispatch()
useEffect(() => {
dispatch(getInvoiceData());
}, [getInvoiceData]);
(...)
export default TableSection;
You need to add dispatch function to dep array:
const TableSection = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(getInvoiceData());
}, [dispatch]);
Its safe to add it to dep array because its identity is stable across renders, see docs.
Note: like in React's useReducer, the returned dispatch function identity is stable and won't change on re-renders (unless you change the store being passed to the , which would be extremely unusual).
This is not an error, its just a warning.
You can fix this by adding dispatch in the dependency array.
useEffect(() => {
dispatch(getInvoiceData());
}, [dispatch]);
second part of the warning message states, Outer scope values like 'getInvoiceData' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps, you also need to remove getInvoiceData function from the dependency array of useEffect hook.
Anything from the scope of the functional component, that participates in react's data flow, that you use inside the callback function of useEffect should be added in the dependency array of the useEffect hook.
Although, in your case, it is safe to omit dispatch function from the dependency array because its guaranteed to never change but still it won't do any harm if you add it as a dependency.

Resources