Attention!!!
The code shown in this article is a valid implementation of the redux pattern referenced from the Official Redux.js.org. However, redux has provided us with an enhanced toolkit which they recommend using when developing with react and redux. It is completely your decesion on which path you take. You can learn more about reduxjs/toolkit by checking out the link.
Overview
In this article, I will cover some fundamental concepts of redux and how to build a simple application(Counter App) using react and redux.
Redux
Redux is predictable state container for javascript applications. This means whenever there are changes within the application including both data and UI changes, those changes are contained in a single javascript object called the state. The state is often referred to as the single-source-of-truth because it is never mutated or modified, but instead recreated. With a single-source-of-truth we can better predict the state of the application at a giving moment.
Redux Fundamentals
Before diving into the application lets cover some Redux fundamentals.
State
The state is a javascript object that represents the entire state of a redux application. It can be a simple object with a single value or a more complex object.
{counter: 0}
The state is accessible throughout the entire application and is managed by a centralized container known as the store. The only way to access the store is by dispatching an action.
Actions
An action is an description of how the store should change the state.
{
type: 'INCREMENT',
value: 5,
}
The change within the application is understood by the action's type property. All actions have a type property. The type property explains to the store how to respond and recreate the state. The creation of the state is handled by a reducer.
Reducers
A reducer is a javascript function that will create a new state based on some action type.
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionType.ADD:
return {
...state,
counter: state.counter + 1
}
}
return state;
}
Reducers are often referred to as pure javascript functions. A pure javascript function is a function that giving the same input will always return the same output. They are called pure functions because they are predictable and include no side effects such as network or database calls. The most import job of a reducer is to recreate the state for the store.
Store
A store is a javascript object that holds the application's state. There should only be a single store in a redux application.
{
dispatch: Dispatch
getState: () => State
subscribe: (listener: () => void) => () => void
replaceReducer: (reducer: Reducer) => void
}
Store Methods
-
dispatch(action)
: Dispatches an action -
getState()
: Returns the current state -
subscribe(listener)
: Adds a change listener -
replaceReducer(nextReducer)
: Replaces the reducer
Redux Pattern
The redux pattern can visualized as below.
React and Redux (Counter App)
In this section, I will cover the steps to build a simple counter application with react and redux. To see the application Code.
Getting Started
First, I'll create a basic react app with npx and install the needed dependencies: react-redux and redux.
npx create-react-app redux-practice
cd redux-practice
npm i react-redux redux
Next, I'll create three new directories to manage the files of my application.
mkdir src/store
mkdir src/containers
mkdir src/components
-
src/store
- Reducers and actions needed for redux store -
src/containers
- Components connected to the redux store -
src/components
- Presentational level components
I'll start off by working on the actions and reducers which will be located in the the store directory.
touch src/store/actions.js
touch src/store/reducers.js
First, I'll define my actions. This application wont have very many for the sake of simplicity and demonstration.
src/store/actions.js
export const ADD = 'ADD';
export const SUBTRACT = 'SUBTRACT';
Next, I'll create the reducer function needed for the store.
src/store/reducers.js
import * as actionType from './actions';
const initialState = {
counter: 0
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionType.ADD:
return {
...state,
counter: state.counter + 1
}
case actionType.SUBTRACT:
return {
...state,
counter: state.counter - 1
}
default:
return state
}
}
export default reducer;
In the above code snippet I've imported all the actions from actions.js , then created a state variable to initialize the state for this reducer, and created my reducer function. The reducer function takes two arguments state, which if not initialized will be set to the initialState, and action which will be passed in once an action is dispatched. I'll use a switch state to determine the action type and using the actions from actions.js handle each case accordingly.
Now that I have my reducer and actions created I'll create my store. The store should be created at the top level component which in this case is index.js
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './store/reducers';
import './index.css';
import App from './App';
const store = createStore(reducer);
ReactDOM.render(
<React.Fragment>
<Provider store={store}><App /></Provider>
</React.Fragment>,
document.getElementById('root')
);
In the index.js file I imported createStore from redux, as well as Provider from react-redux. I also imported my reducers which I will need for my createStore function. The createStore function takes in the reducers and returns the redux store. I use the Provider which has a prop called store and I pass the store created above to the Provider prop. At this point the redux store is accessible throughout the entire react application.
Next, I'll create two components to represent the counter application.
mkdir src/components/CounterButton
mkdir src/components/CounterLabel
touch src/components/CounterButton/CounterButton.js
touch src/components/CounterLabel/CounterLabel.js
These components are presentational components so they will be very simple.
CounterButton.js
import React from 'react';
import './CounterButton.css';
function CounterButton(props) {
return (
<div className="CounterButton">
<button
type="button"
onClick={props.clicked}
className="btn btn-primary">
{props.label}
</button>
</div>
)
}
export default CounterButton;
CounterLabel.js
import React from 'react';
import './CounterLabel.css'
function CounterLabel(props) {
return (
<div className='CounterLabel'>
{props.value}
</div>
)
}
export default CounterLabel;
Next, I'll create the counter component which will be connected to the redux store.
touch src/containers/Counter.js
Counter.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actionType from '../store/actions';
import CounterLabel from
'../components/CounterLabel/CounterLabel';
import CounterButton from
'../components/CounterButton/CounterButton';
import './Counter.css';
class Counter extends Component {
render() {
return (
<div className="Counter">
<CounterLabel value={this.props.ctr} />
<CounterButton
clicked={this.props.onAdd}
label="Add" />
<CounterButton
clicked={this.props.onSubtract}
label="Subtract" />
</div>
)
}
}
const mapStateToProps = state => {
return {
ctr: state.counter
}
}
const mapDispatchToProps = dispatch => {
return {
onAdd: () => dispatch({ type: actionType.ADD }),
onSubtract: () => dispatch({type: actionType.SUBTRACT})
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
Within the Counter.js I import the connect function from react-redux package which is used directly on the component at the end of the file. The connect function takes two arguments, first mapStateToProps which is responsible for selecting a part of the state that the component needs, and second mapDispatchToProps which is responsible for dispatching actions to the store. The onAdd and onSubtract functions are passed as props to the ControlButtons and when clicked dispatch their respective actions to the store. At this point are simple counter application is complete.
When To Use Redux
The application in this article is very simple for learning purposes. In most cases you will not need redux for an application of this size. A state management system is good for large scale applications where state management is difficult to understand. Here are a few pointers on when to use redux that I got from Maxillian over at Academind.
- Local UI State - Redux not recommended
- Persistent State - Redux can be used for the data you need to display
- Client State - Look to use Redux
Conclusion
As always take care and if you found this article helpful please leave a rating or if you have a question leave a comment and I'll try to get back to you as soon as possible.
Top comments (11)
Hi, I'm a Redux maintainer. Please note that "modern Redux" code is very different than what most older tutorials show. We've introduced newer APIs like Redux Toolkit, which is a set of utilities that provide a light abstraction to simplify the most common Redux tasks, and the React-Redux hooks API, which is generally easier to use than the traditional
connect
API.I strongly recommend reading through the newly rewritten official tutorials in the Redux docs, which have been specifically designed to teach you how Redux works and show our recommended practices:
The older patterns shown in almost all other tutorials on the internet are still valid, but not how we recommend writing Redux code today.
You should also read through the Redux "Style Guide" docs page, which explains our recommended patterns and best practices. Following those will result in better and more maintainable Redux apps.
In addition, the easiest way to start a new project is with the official Redux+JS template for Create-React-App. It comes with Redux Toolkit and the React-Redux hooks API already set up when the project is created.
Also how do you become an active contributor of the redux api? I think this would be valuable information for those who are looking for ways to get involved with an open source project. May we connect with directly?
I'm always interested in having folks contribute to Redux! Now, having said that: the Redux core library is stable and there's no active development work going on there. Same with React-Redux for now.
There is some active development work going on with Redux Toolkit, but it's primarily myself and a couple other maintainers atm.
Beyond that, there's a ton of work that I want to do with the Redux docs that I don't have time to do all by myself:
github.com/reduxjs/redux/issues/3592
and I'd definitely love to have folks get involved with that effort. (I actually got started with Redux by contributing the Redux FAQ to the docs back in early 2016.)
I hang out in the
#redux
channel in the Reactiflux Discord ( reactiflux.com ). Feel free to drop by and say hi.I will check this out myself and also advertise this on this article.
I was unable to access the discord channel, but I would be happy to knock out a few of those issues you have on your repository. Do we just fork the repository and work the issues as described?
Hmmm. Anyone should be able to sign up for a Discord account if you don't have one yet, and then join the Reactiflux discord. We do have a welcome flow where you have to agree to the community guidelines before you can access any other channels, so make sure you tried that.
If there's a particular issue you're interested in working on, leave a comment in that issue thread and we can discuss details.
I was able to get access to the channel taking a moment to review the welcome content and I have left a comment on the repo. #3591
Thank you for the feedback. How old are the methods used in this tutorial compared to that of the redux toolkit?
The patterns shown here are the original Redux usage patterns that we've shown since day 1: hand-written immutable update logic,
SCREAMING_SNAKE_CASE
constants, separate "actions" and "reducers" files, and use ofconnect
.The pieces that we now call "modern Redux" have come out over time:
Awesome, thanks for sharing!
Thanks for the post Matt!
One of the best and most overlooked alternatives to Redux is to use React's own built-in Context API.
Context API provides a different approach to tackling the data flow problem between React’s deeply nested components. Context has been around with React for quite a while, but it has changed significantly since its inception. Up to version 16.3, it was a way to handle the state data outside the React component tree. It was an experimental feature not recommended for most use cases.
Initially, the problem with legacy context was that updates to values that were passed down with context could be “blocked” if a component skipped rendering through the shouldComponentUpdate lifecycle method. Since many components relied on shouldComponentUpdate for performance optimizations, the legacy context was useless for passing down plain data.
The new version of Context API is a dependency injection mechanism that allows passing data through the component tree without having to pass props down manually at every level.
The most important thing here is that, unlike Redux, Context API is not a state management system. Instead, it’s a dependency injection mechanism where you manage a state in a React component. We get a state management system when using it with useContext and useReducer hooks.
A great next step to learning more is to read this article by Andy Fernandez: scalablepath.com/react/context-api...