Passing in Redux Actions into useEffect's dependencies - reactjs

I have the predicament of passing in Redux actions into useEffect's dependencies list.
For example,
import { someReduxAction } from '../actions/greatestPathEverMade'
const mapDispatchToProps = () => ({
someReduxAction
})
const DummyComponent = ({ someReduxAction }) => {
useEffect(() => {
someReduxAction()
}, [someReduxAction])
return ( .... )
}
I'm 75% sure that the Redux Action is not changing so I can omit it from useEffect's dependency array but my question is mainly: Is it possible for Redux actions to change? Usually, they are constant in that a basic redux action will dispatch a type and payload object.

The ESLint exhaustive-deps rule has no way to know whether that function will change to a different reference or not, so it will always tell you that the function needs to be added to the dependencies list. The only exceptions are built-in React functions like a useState setter or useReducer dispatch, which React ensures are stable references, and are hardcoded that way into the ESLint rule.

Related

How to memoize useSelector() with createSelector()?

I wish to explain the memoization on a Redux store on my programming website.
What I have now on CodeSandbox:
import { useCallback, useMemo, memo } from "react";
import { useSelector, useDispatch } from "react-redux";
import { createSelector } from "reselect";
const Plus = memo(({ onIncrement }) => {
console.log("rendering <Plus>...");
return <button onClick={onIncrement}>+</button>;
});
export default () => {
console.log("rendering <App>...");
const v = useSelector((state) => {
console.log("useSelector() invoked");
return state;
});
const dispatch = useDispatch();
const increment = useCallback(() => dispatch({ type: "INCREMENT" }), [
dispatch
]);
return (
<div>
<span>{v}</span>
<Plus onIncrement={increment} />
</div>
);
};
As you can see, I have successfully memorized the dispatch() function with useCallback(). However, every time the button is clicked, useSelector() is called twice.
Is there a way to call it once only? I am thinking about memorization with createSelector() from the 'reselect' library. I don't understand that library. Could someone provide some guidance?
References:
Using memorizing selectors 2) The 'reselect' library
They are working correctly by having running the selectors 2 times. It would be easier and more obvious if you also print out the state (the counter) inside the selector.
Reference: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates
Things will be this way:
A button and 0 are rendered on the UI.
You click the button, which dispatches an action.
The selector runs for the first time as it sees an action dispatched, it detects a change in the returned value, so it forces the component to re-render. Log is printed out with the NEW value (not the OLD one).
However, when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison. This is different than connect(), which uses shallow equality checks on the results of mapState calls to determine if re-rendering is needed. This has several implications on how you should use useSelector()
The component re-renders, which triggers the the second log ("rendering ...").
That also leads to a re-run of the selector, which explains the third log.
When the function component renders, the provided selector function will be called and its result will be returned from the useSelector() hook. (A cached result may be returned by the hook without re-running the selector if it's the same function reference as on a previous render of the component.)

Dispatching actions inside inner functions of funtional component

I read that inner functions in statefull functional component should be defined using useCallback when we use setState or that function is passed as prop to child component. But what about dispatching actions? Do we need to use 'useCallback' there also?
import React from "react";
import { logout } from "../../../../actions/auth";
import { useDispatch } from "react-redux";
function Navbar (props) {
...
const dispatch = useDispatch();
const handleClick = () => {
dispatch(logout());
}
return (
<div>
<button onClick={handleClick}>Logout</button>
</div>
)
}
So first of all you need to know why you sometimes need useCallback around your functions and listeners.
The powerful useMemo and useCallback hooks create a value which is referentially stable (prev === current) between re-renders under the condition that the inputs (or “dependencies”) arrays doesn’t change its values (again, referentially). This allows children components you pass your values to to memoize themselves with React.memo and similar tools.
Why would you need to let them memoize? For performance reasons. And the first rule of performance optimization is you don’t optimize prematurely. So, do not blindly wrap everything in useCallback and useMemo: write your code and only once you reach some performance bottleneck investigate and eventually solve with those hooks.
To answer your question, do dispatch functions need to be wrapped? As I infer from your code we are talking about React Redux’ dispatch. The answer is no: React Redux dispatch, useReducer’s dispatch, useState’s updater function are all referentially stable.
dispatch does not effect to your inner functions. Your inner functions will be re-created when you create it without useCallback or with useCallback but dependencies changed
You can put dispatch as a dependency of useCallback and use it like normal function
const handleClick = useCallback(() => {
dispatch(logout());
}, [dispatch])

Using connect and mapDispatchToProps with functional components

I am experimenting with Redux. I understand to dispatch action to the store from functional components, one can subscribe using useDispatch hook. I am dispatching action from useEffect and according to react-redux doc., dispatch can be omitted from list of dependencies to useEffect because its identity is stable .
const TriviaCategories = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch({ type: "SET_CATEGORIES", triviaCategories });
}, []);
return null;
};
If i decide to use connect to subscribe to the store, will the dispatch function identity still be stable like with useDispatch hook?
The dispatch function will always be stable as long as you continue to pass the same Redux store instance to your <Provider store={store}>, because dispatch is simply store.dispatch.
However, the React "hook dependencies" lint rule does not know this. It knows that functions from React's built-in hooks (useState setters and useReducer dispatch functions) are always stable, so it can special-case and ignore those, but it has no way of knowing that the React-Redux dispatch function should be stable. So, it will always warn that you need to add it to the effect deps array.
Because of that, you might as well just always add it to the deps array.

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.

React usEffect firing when it shouldn't be

I have a react component with a prop that is passed by a redux connect method. There is a useEffect linked specifically to that prop that is supposed to perform an async call when it changes. The problem is the useEffect fires any time I change the redux state anywhere else in that app, despite the prop I have the useEffect attached to not changing.
The useEffect method looks like this
useEffect(() => {
if (userPhoneNumber) {
myAsyncFunction()
.then(() => {
showData()
})
}
}, [userPhoneNumber])
And the userPhoneNumber prop is passed via the react-redux connect method like so:
const mapStateToProps = state => {
return {
userPhoneNumber: state.appState.userPhoneNumber
}
}
export default connect(mapStateToProps)(MyComponent)
From what I understand this could be breaking in two potential places. for one, useEffect should not be firing if the userPhoneNumber prop doesn't change. Also, the mapStateToProps method is not returning a new value so it should not be triggering any sort of a rerender.
The redux state changes that are leading to the unexpected useEffect call are coming from sibling components that have a similar react-redux connect setup to this component, with different state to prop mappings.
When a reducer runs you'll have an entirely new state object, thus a new userPhoneNumber prop reference. You should memoize the userPhoneNumber value. I use reselect to create memoized state selectors, but you could probably use the useMemo hook and use that memoized value in the dependency for the effect.
const memoizedUserPhoneNumber = useMemo(() => userPhoneNumber, [userPhoneNumber]);
useEffect(() => {
if (memoizedUserPhoneNumber) {
myAsyncFunction()
.then(() => {
showData()
})
}
}, [memoizedUserPhoneNumber]);
Using Reselect
import { createSelector } from 'reselect';
const appState = state => state.appState || {};
const userPhoneNumber = createSelector(
appState,
appState => appState.userPhoneNumber;
);
...
const mapStateToProps = state => {
return {
userPhoneNumber: userPhoneNumber(state),
}
}
None of this helps keep the entire functional component from re-rendering when the parent re-renders though, so to help with this you'll need to help "memoize" the props being fed into the component with the memo HOC.
export default memo(connect(mapStateToProps)(MyComponent))

Resources