Accessing context from useEffect - reactjs

I have a context that is used to show a full page spinner while my application is performing long running tasks.
When I attempt to access it inside useEffect I get a the react-hooks/exhaustive-deps ESLint message. For example the following code, although it works as expected, states that busyIndicator is a missing dependency:
const busyIndicator = useContext(GlobalBusyIndicatorContext);
useEffect(() => {
busyIndicator.show('Please wait...');
}, []);
This article suggests that I could wrap the function with useCallback which might look as follows:
const busyIndicator = useContext(GlobalBusyIndicatorContext);
const showBusyIndicator = useCallback(() => busyIndicator.show('Please wait...'), []);
useEffect(() => {
showBusyIndicator();
}, [showBusyIndicator]);
Although this works it has moved the issue to the useCallback line which now complains about the missing dependency.
Is it ok to ignore the ESLint message in this scenario or am I missing the something?

If your busyIndicator is not changed during the life of the component, you could simply add it as a dependency to useEffect:
const busyIndicator = useContext(GlobalBusyIndicatorContext);
useEffect(() => {
busyIndicator.show('Please wait...');
}, [busyIndicator]);
If busyIndicator could be changed and you don't want to see an error, then you could use useRef hook:
const busyIndicator = useRef(useContext(GlobalBusyIndicatorContext));
useEffect(() => {
busyIndicator.current.show('Please wait...');
}, []);
The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class. read more

No need to Wrap your useContext in useRef Hook.
you can update your context data just call in useEffect Brackets
like this.
const comingData = useContext(taskData);
useEffect(() => {
console.log("Hi useEffect");
}},[comingData]); //context data is updating here before render the component

Related

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.

React useState hook always comes as undefined when setting it in useEffect

In my React application, I have a useEffect that checks if an element has the display style none set to it. If it does then it should set the state to false, however it always comes back as undefined.
const [testingProp, setTestingProp] = useState();
useEffect(() => {
const styles = getComputedStyle(customerPropertyTypeSection.current);
if (styles.display == 'none') {
setTestingProp(false);
console.log('style set to none'); // this prints
console.log(testingProp); // this prints 'undefined'
}
}, []);
setState in React acts like an async function.
So putting a console.log(state) right after setting it, will most likely show the former value, which is undefined in this case, as it doesn't actually finish updating the state until the log command runs.
You can use a deticated useEffect hook with the relevant state as a dependency to act upon a change in the state.
Example:
useEffect(() => {
console.log(state);
}, [state]);
Basically, the callback function in the example will run every time the state changes.
P.S. - maybe you can do without the useEffect you are using here to populate the state.
If you have access to customerPropertyTypeSection.current initially, you can do something like this:
const [testingProp, setTestingProp] = useState(() => {
const styles = getComputedStyle(customerPropertyTypeSection.current);
return styles.display !== 'none';
});
If the example above works for you, then the useEffect you are using is redundant and can be removed.

How to access redux connect mapStateToProps without a rerender

I am trying to use a mousedown event to close an autocomplete when the user clicks outside of the input.
code:
// listen to mouse clicking outside of autocomplete or input
useEffect(() => {
document.addEventListener("mousedown", handleClickOutsideAutocomplete);
return () => {
document.removeEventListener("mousedown", handleClickOutsideAutocomplete);
};
}, []);
const handleClickOutsideAutocomplete = (e) => {
console.log("props:", props);
const { current: wrap } = wrapperRef;
if (wrap && !wrap.contains(e.target)) {
setisOpen(false);
}
};
This code runs as expected. However, when I try to access props on the mousedown event passed via react-redux connect, they are all null. The props passed from the parent component however are present. I have confirmed that on the initial render the react-redux connect props are there as expected.
I thought the mousedown event may be something to do with it so I tested accessing react-redux connect props using a timeout as follows:
useEffect(() => {
const timer = setTimeout(() => {
console.log("The connect props are all null", props);
}, 5000);
return () => clearTimeout(timer);
}, []);
The react-redux connect props here are also null.
Is it possible to access connect props after the initial render, i.e., on a timeout or mousedown event?
Problem is that you haven't added the handleClickOutsideAutocomplete function in the dependency array of the useEffect hook and because of the closure, event handler function doesn't sees the updated value of props.
Solution
Add the handleClickOutsideAutocomplete in the dependency array of the useEffect hook and also wrap handleClickOutsideAutocomplete in the useCallback hook to avoid running the useEffect hook everytime your component re-renders. Also don't forget to list the required dependencies in the dependency array of the useCallback hook.
useEffect(() => {
...
}, [handleClickOutsideAutocomplete]);
const handleClickOutsideAutocomplete = useCallback((e) => {
...
}, [props]);
React recommends to use exhaustive-deps rule as part of their eslint-plugin-react-hooks package. It warns whenever you omit or incorrectly specify a dependency and also suggests a fix.

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.

Why React expects me to add setters as useEffects dependencies

I am trying to use an async function inside a useEffect callback.
There is a behavior i don't understand (in my case related to the throttle function from lodash).
I don't get what is happing, and how to solve it, here is a sample of the code:
import throttle from 'lodash/throttle';
const myRequestWrapped = throttle(myRequest, 300);
const [name, setName] = useState('');
useEffect(() => {
myRequest(name) // No warning
myRequestWrapped(name); // React Hook useEffect has a missing dependency: 'myRequestWrapped'. Either include it or remove the dependency array
}, [ name ]);
If i add myRequestWrapped as a dependency, i have an infinite loop (the effect is triggered continuously).
I guess the throttle method works with a timer and it returns a different result at every run so i can understand why the infinite loop.
But i really don't understand why React wants it as a dependency (especially that it works without adding it !).
What is the logic?
Why myRequestWrapped and not myRequest?
Should i ignore the warning or do you know a clean way to solve that?
Thanks.
Its not React that wants you to add myRequestWrapped as a dependency but its eslint.
Also you must note that ESLint isn't aware of the programmers intention so it just warns the user if there is a scope of error being made.
Hooks heavily rely on closures and sometimes its difficult to figure out bugs related to closures and that is why eslint prompts if there is a case of a fucntion of variabled used within useEffect that might reflect the updated values.
Of course the check isn't perfect and you could carefully decide whether you need to add a dependency to useEffect or not.
If you see that what you wrote is perfectly correct. You can disable the warning
useEffect(() => {
myRequest(name);
myRequestWrapped(name);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ name ]);
Also you must not that throttle function cannot be used within render of functional componentDirectly as it won't be effective if it sets state as the reference of it will change
The solution here is to use useCallback hook. Post that even if you add myRequestWrapped as a dependency to useEffect you won't be seeing an infinite loop since the function will only be created once as useCallback will memoize and return the same reference of the function on each render.
But again you must be careful about adding dependency to useCallback
import throttle from 'lodash/throttle';
const Comp = () => {
const myRequestWrapped = useCallback(throttle(myRequest, 300), []);
const [name, setName] = useState('');
useEffect(() => {
myRequest(name);
myRequestWrapped(name);
}, [ name ]);
...
}
Shubham is right: it's not REACT, but ESLint instead (or TSLint, depending on the Linter you are using).
If I may add, the reason why the Linter suggest you to add myRequestWrapped is because of how the closures work in JavaScript.
To let you understand, take this easier example (and I need this because I would need to know what it's inside myRequestWrapped:
const MyComp = props => {
const [count, setCount] = React.useState(0);
const handleEvent = (e) => {
console.log(count);
}
React.useEffect(() => {
document.body.addEventListener('click', handleEvent);
return () => { document.body.removeEventListener('click', handleEvent); }
}, []);
return <button onClick={e => setCount(s => s +1)}>Click me</button>
}
So, right now, when MyComp is mounted, the event listener is added to the document.body, and anytime the click event is triggered, you call handleEvent, which will log count.
But, since the event listener is added when the component is mounted, the variable count inside handleEvent is, and always will be, equal to 0: that is because the instance of handleEvent created when you added the event listener is just one, the one that you associated with the event listener.
Instead, if you would write the useEffect like this:
React.useEffect(() => {
document.body.addEventListener('click', handleEvent);
return () => { document.body.removeEventListener('click', handleEvent); }
}, [handleEevent]);
Anytime the handleEvent method is updated, also your event listener is updated with the one handleEvent, thus when clicking on the document.body you will always log the latest count value.

Resources