2

Getting Started With Redux: Connecting Redux With React

 1 year ago
source link: https://code.tutsplus.com/tutorials/getting-started-with-redux-connecting-redux-with-react--cms-30352
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Getting Started With Redux: Connecting Redux With React

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 and 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. We created a Redux store 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 have learned:

  1. the difference between container components and presentational components
  2. about the react-redux library and the redux-js-toolkit
  3. how to bind react and redux using connect()
  4. how to dispatch actions using mapDispatchToProps
  5. how to retrieve state using mapStateToProps
  6. how to dispatch actions and get the state using the new Redux hooks: useDispatch  and useSelector 

The code for the tutorial is available on GitHub in the react-redux-demo repo. Grab the code from the main 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 v2 branch. Let's get started.

Designing a Component Hierarchy: Smart vs. Dumb Components

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 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.

Moving on, let's see how we're going to organize our components.

Designing component Hierarchy

Designing component Hierarchy

Designing component Hierarchy

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}) =>
(
<form>
<div className="form-group">
<label htmlFor="emailAddress">Email address</label>
<input type="email" class="form-control" name="email" onChange={onInputChange} placeholder="[email protected]" />
</div>
{/* Some code omitted for brevity */}
<div className="form-group">
<label htmlFor="physicalAddress">Address</label>
<textarea className="form-control" name="address" onChange={onInputChange} rows="3"></textarea>
</div>
<button type="submit" onClick={onFormSubmit} class="btn btn-primary"> Submit </button>
</form>
)
export default AddContactForm;

This is an HTML form for adding a new contact. The component receives onInputChange and onFormSubmit callbacks 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( <ul className="list-group" id="contact-list">
{props.contactList.map(
(contact) =>
<li key={contact.email} className="list-group-item">
<ContactCard contact = {contact}/>
</li>
)}
</ul>)
}
export default ContactList;

This component receives an array of contact objects as props, hence the name ContactList. We use the Array.map() method to extract individual contact details and then pass on that data to <ContactCard />.

components/ContactCard.jsx

const ContactCard = ({contact}) => {
return(
<div>
<div className="col-xs-4 col-sm-3">
{contact.photo !== undefined ?  <img src={contact.photo} alt={contact.name} className="img-fluid rounded-circle" /> :
<img src="img/profile_img.png" alt ={contact.name} className="img-fluid rounded-circle" />}
</div>
<div className="col-xs-8 col-sm-9">
<span className="name">{contact.name + ' ' + contact.surname}</span><br/>
{/* Some code omitted for brevity */}
</div>
</div>
)
}
export default ContactCard;

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 bare-bones container components.

containers/Contacts.jsx

function Contacts(props) {
const returnContactList = () => {
// Retrieve contactlist from the store
}
return (
<div>  
<AddContact/>
<br />
<ContactList contactList={returnContactList()} />
</div>
);
}
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/AddContact.jsx

function AddContact() {
const shouldAddContactBox = () => {
/* Logic for toggling ContactForm */
}
const handleInputChange = (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
/* Logic for handling Input Change */
}
const handleSubmit = (e) => {
e.preventDefault()
/* Logic for hiding the form and update the state */
}
const renderForm = () => {
return(
<div className="col-sm-8 offset-sm-2">
<AddContactForm onFormSubmit={handleSubmit} onInputChange={handleInputChange} />
</div>
)
}
return(
<div>           
{ /* A conditional statement goes here that checks whether the form
should be displayed or not */}
</div>
)
}
export default AddContact;

We've created three bare-bones handler methods that correspond to the three actions. They all dispatch actions to update the state. 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.

Advertisement

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 many important APIs including a <Provider /> component, a higher-order function known as connect() and utility hooks like useSelector() and useDispatch().

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 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(
<Provider store={store}>
<App />
</Provider>,
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 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 (
<div>
{newContact.name} <br />
{newContact.email} <br />
{newContact.phone} <br />
Are you sure you want to add this contact?
<span onClick={addContact}> Yes </span>
</div>
)
}
const mapStateToProps = state => {
return {
newContact : state.contacts.newContact
}
}
const mapDispatchToProps = dispatch => {
return {
addContact : () => dispatch(addContact())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddContact)

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 <AddContact/> component.

Advertisement

Simplifying the Code with Redux Hooks

We learnt how to connect our React component to the state in the previous section. The problem with the technique used above is the volume of code we had to write. We had to repeat functions to map the state to the action dispatcher and the component to the store. This may become an even bigger problem for large codebases.

Fortunately, some utilities were added to the React Redux library with the sole aim of decreasing the amount of boilerplate, and one of those utility is the useSelector hook. With this hook, you don't need to map anything nor do you need connect()—just import the hook and use it to access your application state anywhere in your app.

Demo Code

import {useSelector, useDispatch} from 'react-redux'
const AddContact = ({newContact, addContact}) => {
const dispatch = useDispatch()
const newContact = useSelector(state => state.contact.newContact)
return (
<div>
{newContact.name} <br />
{newContact.email} <br />
{newContact.phone} <br />
Are you sure you want to add this contact?
<span onClick={dispatch(addContact)}> Yes </span>
</div>
)
}

Another hook3useDispatch()—was used above to dispatch an action on clicking the span element. Compared to the code in the previous section, you would agree that this one is cleaner and easier to understand. There is also no code repetition, making it very useful when dealing with large codebases.

You should note that these hooks were introduced starting from React Redux v7.1, so you must install either that or a later version in order to use them.

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 <Provider />.

index.js

import React from 'react';
import {render}from 'react-dom';
import { Provider } from 'react-redux'
import App from './App';
import makeStore from './store'
const store = makeStore();
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

Connect React Containers to Redux to Use State

The connect function is used to bind React containers to Redux. What that means is that you can use the connect feature to:

  1. subscribe to the store and map its state to your props
  2. dispatch actions and map the dispatch callbacks into your props

However, we'll no longer use the connect function to connect our store. Instead, we'll use the hooks to fetch from our store and dispatch actions when the need arises.

First, import both useSelector, useDispatch and the actions you want to dispatch into AddContact.jsx.

import { useSelector, useDispatch } from 'react-redux';
import { addContact, handleInputChange, toggleContactForm } from '../actions/';

Second, inside the AddContact() function, on the first line, import the state that the component needs and get the dispatcher:

const isHidden = useSelector(state => state.ui.isAddContactFormHidden)
const newContact = useSelector(state => state.contacts.newContact)
const dispatch = useDispatch()

The component is now equipped to read state from the store and dispatch actions. Next, the logic for handeInputChange, handleSubmit and showAddContactBox should be updated as follows:

showAddContactBox() {
dispatch(toggleContactForm())      
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
dispatch(handleInputChange(name, value))       
}
handleSubmit(e) {
e.preventDefault();
dispatch(toggleContactForm())      
dispatch(addContact())             
}

We've defined the handler methods, but there is still one part missing—the conditional statement inside the render function.

return(
<div>        
{ isHidden === false ? enderForm(): <button onClick={showAddContactBox} className="btn"> Add Contact </button>}
</div>
)

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 from 'react';
import { useSelector } from 'react-redux';
/* Component import omitted for brevity */
function Contacts() {
const contactList = useSelector(state => state.contacts.contactList)    
const returnContactList = () => {
return contactList;
}
return (
<div>
<br />
<AddContact/>
<br />
<ContactList contactList= {returnContactList()} />
</div>
); 
}
export default Contacts

We've gone through the same procedure that we followed above to connect the Contacts component with the Redux store in that we used useSelector to grab the needed branch of the state, which is contactList. 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 middleware and start dispatching actions that involve fetching data from the server. Share your thoughts in the comments!

This post has been updated with contributions from Kingsley Ubah. Kingsley is passionate about creating content that educates and inspires readers. Hobbies include reading, football and cycling.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK