With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.
In this article, we’ll look at how to use the useDispatch
and useStore
hooks in the React-Redux hooks API, and also look at performance and stale state issues.
useDispatch
We can use the useDispatch
hook to get the dispatch
function to dispatch actions to the store.
For example, we use useDispatch
as follows:
import React from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore } from "redux";
function count(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
const store = createStore(count);
function App() {
const count = useSelector(state => state);
const dispatch = useDispatch();
return (
<div className="App">
<button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
<p>{count}</p>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
In the code above, we called useDispatch
in App
to get the dispatch
function so that we can dispatch actions from the store.
Then we create functions that call dispatch
to pass into the onClick
prop.
useStore
We can use the useStore
hook to get the reference to the Redux store. It’s the same one that we passed into the Provider
component.
This shouldn’t be used frequently since we aren’t supposed to access the store directly within a React app since it doesn’t update according to the React update cycle.
Stale Props and “Zombie Children”
Stale props mean that a selector function relies on a component’s props to extract data, a parent component would re-render and pass down new props as a result of an action, but the component’s selector functions execute before the component has a chance to re-render with those new props.
This causes outdated data to be displayed. in the child component or an error being thrown.
“Zombie child” refers to the cases where multiple nested connected components are mounted ina first pass, causing a child component to subscribe to the store before its parent. Then an action is dispatched that deletes data from the store. The parent component would stop rendering the child as a result.
Because the child subscribed to the store first, its subscription runs before the parent stops render. When it reads a value based on props, the data no longer exists. Then an error may be thrown when the extraction logic doesn’t check for the case of the missing data.
Therefore, we should rely on props in our selector function for extracting data.
connect
adds the necessary subscription to the context provider and delays evaluating child subscriptions until the connected component has re-rendered. Putting a connected component in the component tree just above the component using useSelector
will prevent the issues above.
Performance
useSelector
will do a reference equality comparison of the selected value when running the selector function after an action is dispatched.
It’ll only re-render if the previous state is different from the current state. connect
and useSelector
doesn’t prevent the component from re-rendering because of its parent re-rendering.
To stop parent re-rendering from re-rendering the child, we can use memoization to prevent that.
For example, we can write the following:
import React from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore } from "redux";
function count(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
const store = createStore(count);
function App() {
const count = useSelector(state => state);
const dispatch = useDispatch();
return (
<div className="App">
<button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
<p>{count}</p>
</div>
);
}
App = React.memo(App);
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
In the code above, we added the React.memo
call as follows:
App = React.memo(App);
to cache existing results and preventing re-render if the state remains the same.
Conclusion
We can use the useDispatch
hook to get the dispatch
function to update the store’s state.
There’s also a useStore
hook to get a reference of the Redux store in our React components.
However, we shouldn’t access the store directly with useStore
since we’re supposed to use useSelector
to get the latest state from the store and use useDispatch
to dispatch actions.
Also, we have to be aware of stale props from parent components and child components that have been removed because of state changes throwing errors and displaying incorrect data.
We should also memoize the state of components to cache them so that they don’t have to re-render when nothing changed.