An Interest In:
Web News this Week
- May 1, 2024
- April 30, 2024
- April 29, 2024
- April 28, 2024
- April 27, 2024
- April 26, 2024
- April 25, 2024
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.
- Designing a Component Hierarchy: Smart vs. Dumb Components
- Presentational Components
- Container Components
- The react-redux Library
- The Provider Component
- The connect() Method
- Simplifying the Code with Redux Hooks
- How to Connect React and Redux
- Connect React Containers to Redux to Use State
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:
- the difference between container components and presentational components
- about the react-redux library and the redux-js-toolkit
- how to bind react and redux using
connect()
- how to dispatch actions using
mapDispatchToProps
- how to retrieve state using
mapStateToProps
- how to dispatch actions and get the state using the new Redux hooks:
useDispatch
anduseSelector
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.
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.
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.
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:
- subscribe to the store and map its state to your props
- 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.
Original Link: https://code.tutsplus.com/tutorials/getting-started-with-redux-connecting-redux-with-react--cms-30352
TutsPlus - Code
Tuts+ is a site aimed at web developers and designers offering tutorials and articles on technologies, skills and techniques to improve how you design and build websites.More About this Source Visit TutsPlus - Code