This is the third part of the series on Getting Started with Redux and in this tutorial, we’re going to learn how to connect a Redux store with React. Redux is an independent library that works with all the popular front-end libraries & frameworks. And it works flawlessly with React because of its functional approach.
You don’t need to have followed the previous parts of this series for this tutorial to make sense. If you’re here to learn about using React with Redux, you can take the Quick Recap below and then check out the code from the previous part and start from there.
Quick Recap
In the first post, we learned about the Redux workflow and answered the question, Why Redux?. We created a very basic demo application and showed you how the various components of Redux—actions, reducers, and the store— are connected.
In the previous post, we started building a contact list application that lets you add contacts and then displays them as a list. A Redux store was created for our contact list and we added a few reducers and actions. We attempted to dispatch actions and retrieve the new state using store methods like store.dispatch()
and store.getState()
.
By the end of this article, you’ll learn
- the difference between container components and presentational components
- about the react-redux library
- how to bind react and redux using
connect()
- how to dispatch actions using
mapDispatchToProps
- how to retrieve state using
mapStateToProps
The code for the tutorial is available on GitHub at the react-redux-demo repo. Grab the code from the v2 branch and use that as a starting point for this tutorial. If you’re curious to know how the application looks by the end of this tutorial, try the v3 branch. Let’s get started.
Designing a Component Hierarchy: Smart vs. Dumb Component
This is a concept that you’ve probably heard of before. But let’s have a quick look at the difference between smart and dumb components. Recall that we had created two separate directories for components, one named containers/, and the other components/. The benefit of this approach is that the behavior logic is separated from the view.
The presentational components are said to be dumb because they are concerned about how things look. They are decoupled from the business logic of the application and receive data and callbacks from a parent component exclusively via props. They don’t care if your application is connected to a Redux store if the data is coming from the local state of the parent component.
The container components, on the other hand, deal with the behavioral part and should contain very limited DOM markup and style. They pass the data that needs to be rendered to the dumb components as props.
I’ve covered the topic in-depth in another tutorial, Stateful vs. Stateless Components in React.
-
ReactStateful vs. Stateless Functional Components in React
Moving on, let’s see how we’re going to organize our components.
Presentational Components
Here are the presentational components that we’ll be using in this tutorial.
components/AddContactForm.jsx
import React from 'react'; const AddContactForm = ({onInputChange, onFormSubmit}) => () export default AddContactForm;
This is an HTML form for adding a new contact. The component receives onInputChange
and onFormSubmit
callback as props. The onInputChange
event is triggered when the input value changes and onFormSubmit
when the form is being submitted.
components/ContactList.jsx
const ContactList = (props) => { return(
-
{props.contactList.map(
(contact) =>
-
This component receives an array of contact objects as props and hence the name ContactList. We use the Array.map()
method to extract individual contact details and then pass on that data to
.
components/ContactCard.jsx
const ContactCard = ({contact}) => { return() } export default ContactCard;{contact.photo !== undefined ? : }{contact.name + ' ' + contact.surname}
{/* Some code omitted for brevity */}
This component receives a contact object and displays the contact’s name and image. For practical applications, it might make sense to host JavaScript images in the cloud.
Container Components
We’re also going to construct barebones container components.
containers/Contacts.jsx
class Contacts extends Component { constructor(props) { super(props); this.returnContactList = this.returnContactList.bind(this); } returnContactList() { // Retrieve contactlist from the store } render() { return (); } } export default Contacts;
The returnContactList()
function retrieves the array of contact objects and passes it to the ContactList component. Since returnContactList()
retrieves the data from the store, we’ll leave that logic blank for the moment.
containers/AddContacts.jsx
class AddContact extends Component { constructor(props) { super(props); /* Function binding goes here. Omitted for brevity */ } showAddContactBox() { /* Logic for toggling ContactForm */ } handleInputChange(event) { const target = event.target; const value = target.value; const name = target.name; /* Logic for handling Input Change */ } handleSubmit(e) { e.preventDefault(); /* Logic for hiding the form and update the state */ } /* Renders the AddContactForm */ renderForm() { return() } render() { return( { /* A conditional statement goes here that checks whether the form should be displayed or not */}) } } export default AddContact;
We’ve created three barebones handler method that corresponds to the three actions. They all dispatch actions to update the state. In the render method, we’ve left out the logic for showing/hiding the form because we need to fetch the state.
Now let’s see how to bind react and redux together
The react-redux Library
React bindings are not available in Redux by default. You will need to install an extra library called react-redux first.
npm install --save react-redux
The library exports just two APIs that you need to remember, a
component and a higher-order function known as connect()
.
The Provider Component
Libraries like Redux need to make the store data accessible to the whole React component tree starting from the root component. The Provider pattern allows the library to pass the data from top to the bottom. The code below demonstrates how Provider magically adds the state to all the components in the component tree.
Demo Code
import { Provider } from 'react-redux' ReactDOM.render(, document.getElementById('root') )
The entire app needs to have access to the store. So, we wrap the provider around the app component and then add the data that we need to the tree’s context. The descendants of the component then have access to the data.
The connect()
Method
Now that we’ve provided the store to our application, we need to connect the React to the store. The only way that you can communicate with the store is by dispatching actions and by retrieving the state. We’ve previously used store.dispatch()
to dispatch actions and store.getState()
to retrieve the latest snapshot of the state. The connect()
lets you do exactly this, but with the help of two methods known as mapDispatchToProps
and mapStateToProps
. I have demonstrated this concept in the example below:
Demo Code
import {connect} from 'react-redux' const AddContact = ({newContact, addContact}) => { return ({newContact.name}) } const mapStateToProps = state => { return { newContact : state.contacts.newContact } } const mapDispatchToProps = dispatch => { return { addContact : () => dispatch(addContact()) } } export default connect( mapStateToProps, mapDispatchToProps )(AddContact)
{newContact.email}
{newContact.phone}
Are you sure you want to add this contact? Yes
mapStateToProps
and mapDispatchToProps
both return an object and the key of this object becomes a prop of the connected component. For instance, state.contacts.newContact
is mapped to props.newContact
. The action creator addContact()
is mapped to props.addContact
.
But for this to work, you need the last line in the code snippet above.
export default connect( mapStateToProps, mapDispatchToProps )(AddContact)
Instead of exporting the AddContact
component directly, we’re exporting a connected component. The connect provides addContact
and newContact
as props to the
component.
How to Connect React and Redux?
Next, we’re going to cover the steps that you need to follow to connect React and Redux.
Install the react-redux Library
Install the react-redux library if you haven’t already. You can use NPM or Yarn to install it.
npm install react-redux --save
Provide the Store to your App Component
Create the store first. Then, make the store object accessible to your component tree by passing it as a prop to
.
index.js
import React from 'react'; import {render}from 'react-dom'; import { Provider } from 'react-redux' import App from './App'; import configureStore from './store' const store = configureStore(); render(, document.getElementById('root') )
Connect React Containers to Redux
The connect function is used to bind React container to Redux. What that means is that you can use the connect feature to:
- subscribe to the store and map its state to your props
- dispatch actions and map the dispatch callbacks into your props
Once you’ve connected your application to Redux, you can use this.props
to access the current state and also to dispatch actions. I am going to demonstrate the process on theAddContact
component. AddContact
needs to dispatch three actions and get the state of two properties from the store. Let’s have a look at the code.
First, import connect
into AddContact.jsx.
import { connect } from 'react-redux';
Second, create two methods mapStateToProps
and mapDispatchToProps
.
function mapStateToProps(state) { return { isHidden : state.ui.isAddContactFormHidden, newContact: state.contacts.newContact } } function mapDispatchToProps(dispatch) { return { onFormSubmit: (newContact) => { dispatch(addContact(newContact)); }, onInputChange: (name,value) => { dispatch(handleInputChange(name,value)); }, onToggle: () => { dispatch(toggleContactForm()); } } }
mapStateToProps
receives the state of the store as an argument. It returns an object that describes how the state of the store is mapped into your props. mapDispatchToProps
returns a similar object that describes how the dispatch actions are mapped to your props.
Finally, we use connect
to bind the AddContact
component to the two functions as follows:
export default connect(mapStateToProps, mapDispatchToProps) (AddContact)
Update the Container Components to Use the Props
The component’s props are now equipped to read state from the store and dispatch actions. The logic for handeInputChange
, handleSubmit
and showAddContactBox
should be updated as follows:
showAddContactBox() { const { onToggle } = this.props; onToggle(); } handleInputChange(event) { const target = event.target; const value = target.value; const name = target.name; const { onInputChange } = this.props; onInputChange(name,value); } handleSubmit(e) { e.preventDefault(); this.props.onToggle(); this.props.onFormSubmit(); }
We’ve defined the handler methods. But there is still one part missing—the conditional statement inside the render
function.
render() { return({ this.props.isHidden === false ? this.renderForm(): }) }
If isHidden
is false, the form is rendered. Otherwise,a button gets rendered.
Displaying The Contacts
We’ve completed the most challenging part. Now, all that’s left is to display these contacts as a list. The Contacts
container is the best place for that logic.
import React, { Component } from 'react'; import { connect } from 'react-redux'; /* Component import omitted for brevity */ class Contact extends Component { constructor(props) { super(props); this.returnContactList = this.returnContactList.bind(this); } returnContactList() { return this.props.contactList; } render() { return (); } } function mapStateToProps(state) { return { contactList : state.contacts.contactList, } } export default connect(mapStateToProps, null) (Contact);
We’ve gone through the same procedure that we followed above to connect the Contacts component with the Redux store. The mapStateToProps
function maps the store object to the contactList
props. We then use connect to bind the props value to the Contact component. The second argument to the connect is null because we don’t have any actions to be dispatched. That completes the integration of our app with the state of the Redux store.
What Next?
In the next post, we’ll take a deeper look at middlewares and start dispatching actions that involve fetching data from the server. Share your thoughts in the comments!
Powered by WPeMatico