My Learning Note on Redux (ii)

Image for post
Image for post
Photo by Roksolana Zasiadko on Unsplash

Following up to the last post, we’ve discussed about the to set up the redux, and go through 3 out of 4 data flow steps, now we are moving to react UI components that will call the action and receive the state update.

So in essence, what we need to do is just to let a container component call store.subscribe() to read a part of the Redux state tree and supply props to a UI component it renders. We could write the container component by hand, but it’s much easier to use React Redux library's connect() function, which provides many useful optimizations to prevent unnecessary re-renders.

Under components create CakeContainer.js :

function CakeContainer(props) {
return (
<div>
<h2>Number of cakes — {props.numberOfCakes}</h2>
<button onClick={props.buyCake}>Buy Cake</button>
</div>
);
}
const mapStateToProps = (state) => {
//state here is the entire Redux store state (the same value //returned by a call to store.getState()).
return {
numberOfCakes: state.cake.numberOfCakes,
};
};
const mapDispatchToProps = (dispatch) => {
return {
buyCake: () => {
dispatch(buyCake());

},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(CakeContainer);

So what is a connect() function? It connects a React component to a Redux store and provides the React component with:

  • data it needs as props by transforming the state from Redux store
  • functions it can use to dispatch action to the store

And as you can expect from their names, this is done through mapStateToProps and mapDispatchToProps, respectively. state and dispatch will be supplied to your mapStateToProps or mapDispatchToProps functions as the first argument.

mapStateToProps:

This is the first parameter to be passed to connect(). If a mapStateToProps function is specified as in above example, the new wrapper component will subscribe to Redux store updates. This means that any time the store is updated, mapStateToProps will be called. It’s frequently referred to as just mapState for short.

  • It receives the entire store state, and should return an object of data this component needs. (in our case: state.cake.numberOfCakes)
  • It runs every time the store state changes. The values in the returned object field will be used to determine if your component needs to re-render (See below)
  • Each field in the object will become a prop for your actual component
Image for post
Image for post

Also it’s worth mentioning that mapStateToProps can do more than just return state.someSlice — it should reshape the data from the store . They could reshape the data to get the prop needed such as filtering an array, mapping an array of IDs to their corresponding objects, or extracting plain JS values from Immutable.js objects.

mapDispatchToProps

This is the second parameter to connect() . With React Redux, your components never access the store directly — connect does it for you. React Redux gives you two ways to let components dispatch actions:

  • By default, a connected component receives props.dispatch and can dispatch actions itself.
  • connect can accept an argument called mapDispatchToProps, which lets you create functions that dispatch when called, and pass those functions as props to your component.

In the first way, you don’t need to pass inmapDispatchToProps , can leave it as null or empty:

In this way, your component will receive the dispatch prop by default:

If the mapDispatchToProps is declared, it will also be given the dispatch of your store. The function should return a plain object that each field in the object will become a separate prop for your own component, and the value should normally be a function that dispatches an action when called. (()=>{dispatch(buyCake())})

But why would we want to do this?

First, encapsulation. By wrapping the dispatch logic into function makes the implementation more declarative. Dispatching an action and letting the Redux store handle the data flow is how to implement the behavior, rather than what it does.

For example when we buy the cake, it’s easier and more user friendly when we just call a function buyCake instead of having a bizarre dispatch with a type object.

// button unaware of "dispatch",
<button onClick={buyCake} />

The mapDispatchToProps functions are expected to return an object. Each fields of the object should be a function, calling which is expected to dispatch an action to the store.

Secondly, this makes passing down action to child components that might be unconnected easier.

Coming back to mapDispatchToProps, note that you can also forward arguments to your actions:


const mapDispatchToProps = (dispatch) => {
return {
buyCake: (number) => {
dispatch(buyCake(number));
},
};
};

OwnProps

For both mapStateToProps and mapDispatchToProps you can pass in an optional second param: ownProps.

This is when our component needs the data from its own props to retrieve data from the store. For example we can have a combined ItemContainer.js where you can buy either cake or iceCream:

function ItemContainer(props) {
return (
<div>
<h2>Item -{props.item} </h2>
<button onClick={props.buyItem}>Buy Item</button>
</div>
);
}
const mapStateToProps = (state, ownProps) => {
const itemState = ownProps.cake
? state.cake.numberOfCakes
: state.iceCream.numberOfIceCreams;
return {
item: itemState,
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
const dispatchFunction = ownProps.cake
? () => dispatch(buyCake())
: () => dispatch(buyIceCream());
return {
buyItem: dispatchFunction,
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ItemContainer);//somewhere in parent component, e.g. App.js returns : <ItemContainer cake />
// and our ItemContainer receives props.cake, props.item, and props.buyItem

You can also do a bit more complex operation with the ownProps like a function call:

<ConnectedTodo id={123} />

Hooks

React Redux now offers a set of hook APIs as an alternative to the existing connect() Higher Order Component.

useSelector()

This hook allows you to extract data from the Redux store state, using a selector function. It is equivalent to the mapStateToProps argument to connect . The selector will be called with the entire Redux store state as its only argument and run whenever the function component renders.

useDispatch()

Similarly, this hook returns a reference to the dispatch function from the Redux store.

function HooksCakeContainer() {
const numOfCakes = useSelector((state) => state.cake.numberOfCakes);
const dispatch = useDispatch();

return (
<div>
<h2>Num of Cakes — {numOfCakes}</h2>
<button onClick={() => dispatch(buyCake())}>Buy Cake</button>
</div>
);
}
export default HooksCakeContainer;

Provider

The last thing which we almost forget and that is actually the premise for our earlier code is the provider:

function App() {
return (
<Provider store={store}>
<div className=”App”>
<CakeContainer />
</div>
</Provider>
);
}
export default App;

The <Provider /> makes the Redux store available to any nested components that have been wrapped in the connect() function.

Since any React component in a React Redux app can be connected, most applications will render a <Provider> at the top level, with the entire app’s component tree inside of it.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store