Introduction to React Redux
March 09, 2020
The goals of this lecture are to:
- Combine a Redux store with a React app
- Pass state from the store to a component with useSelector
- Dispatch actions to the store with useDispatch
React-Redux
As we incorporate Redux into our React applications, we'll be using a library called react-redux
.
By wrapping our application in a top-level component called <Provider>
, react-redux
will allow us to connect a store for the rest of our application to use.
But, in order for this to work, we'll need a reducer.
Counting Reducer
Here is the reducer from the previous lecture:
const INITIAL_STATE = { count: 0 };
function rootReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case "INCREMENT":
return { ...state, count: state.count + 1 };
case "DECREMENT":
return { ...state, count: state.count - 1 };
default:
return state;
}
}
export default rootReducer;
When an action is sent to our reducer, depending on what type
it is, the state returned will either have a count of plus or minus 1.
The react-redux
library gives us a component named <Provider>
.
This component accepts a Redux store as one of the props.
To give the rest of our application access to this store, we need to wrap our App
component in the react-redux
-provided Provider
component:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import rootReducer from "./rootReducer";
import { createStore } from "redux";
import { Provider } from "react-redux";
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Connecting Components
Now that our application has been provided access to a store, we can begin interacting with the store directly from components within App
.
Accessing the Store
To access values in the store, we'll use the useSelector
hook.
The useSelector
hook accepts a callback. This callback function uses the store as its first argument and returns whatever data we want to retrieve from the store:
useSelector(store => store.count);
Let's see the useSelector
hook in action:
import React from "react";
import { useSelector } from "react-redux";
function FirstCounter() {
// Accessing count from the store
const count = useSelector(store => store.count);
return (
<div>
<h2>The count is: {count}.</h2>
</div>
);
}
export default FirstCounter;
We can see on Line 6 that we are returning the property count
from the store, then displaying it within our component's h2
tag.
Dispatching to the Store
In the previous section, we saw how to access properties from the store.
What if we'd like to make changes to the store?
To make changes, we'll use a different hook, useDispatch
, which will allow us to dispatch actions directly to the store we're connected to.
Take a look at an example below:
import React from "react";
import { useSelector, useDispatch } from "react-redux";
function SecondCounter() {
const count = useSelector(st => st.count);
const dispatch = useDispatch();
const up = () => dispatch({ type: "INCREMENT" });
const down = () => dispatch({ type: "DECREMENT" });
return (
<div>
<h2>The count is: {count}.</h2>
<button onClick={up}> + </button>
<button onClick={down}> - </button>
</div>
);
}
export default SecondCounter;
On Line 6, you can see that we're first setting the return value of our useDispatch
hook.
We are then declaring two functions (up
and down
), each of which will invoke the dispatch
function with a particular action as the argument.
useDispatch
is a reference to the dispatch
function from the Redux store.
Data Flow
Let's review the data flow when using Redux in a React application.
- Store is created. On creation of the store, an initial action is dispatched.
- Reducer returns the initial state. In the example code we've been working with, we've set this state to correspond to
INITIAL_STATE
at the top of ourrootReducer.js
file. - useSelector hook runs for all connected components. Each component will receive the data they've expressed interest in from the store. The update of these state values will trigger a
render
of the component. - dispatch that results in new data will cause re-render. From that point onward, if any component receives new data from the store after dispatching, a re-render of the component will occur.
When to Connect
Not every component will need the useSelector
and/or useDispatch
hooks.
Some components will only need to read data, while others might only need to write.
If you're mapping over an array to render components, you still will continue to pass props directly from parent to child components.
As you continue getting practice with component design, think carefully about when to use the store versus just passing props.
Redux Dev Tools
There is a particular set of Developer Tools specifically for use with Redux, aptly named Redux Dev Tools.
As you move forward with your React-Redux application development, we encourage you to setup the extension for the browser of your choice: Redux Dev Tools
Including Redux Dev Tools
In your index.js
file, include the following:
const store = createStore(rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__
&& window.__REDUX_DEVTOOLS_EXTENSION__()
);
With that snippet in place, you'll now have access to the application's Redux state directly in your browser!