Redux for React: A Simple Introduction

Ross Bulat
Jul 25, 2018 · 8 min read

Introduction

A state management tool becomes a necessity in React apps as your state increases in complexity, one such solution being Redux. Redux allows you to manage your entire application state in one object, called the Store.

Updates to the Store will trigger re-renders of components that are connected to the part of the store that was updated. When we want to update something, we call an Action. We also create functions to handle these actions and return the updated Store. These functions are called Reducers.

Having a “State Tree” as opposed to hundreds of states in hundreds of components makes a lot of sense. However, you are not forced to only use Redux to store your state: there are a few scenarios where using a global state object does not make sense, like forms.

Why? Forms are isolated pieces of your application, the state of which is not needed to be known outside of that form. (No external components need to know what fields have been validated, whether the form is submitting, the values of that form, etc). It is the result of the form that would be of use, as the app relies on that result to make changes to its state.

Ok, so — one big state object, great. How do we connect components to the state? How do we submit changes to the state? Let’s answer those questions.

This article will firstly explain how to initialise your Store (Redux state), followed by actions and reducer explanations, and more.

In short, we will cover:

  • Installing Redux packages
  • Actions and Reducers with examples
  • Creating your initial store with createStore()
  • Dispatching actions with dispatch()
  • Connecting components to Redux with connect(), and the idea of container vs presentational components
  • Some template files to get you started quickly.

Installation

Install redux and react-redux packages into your project.

npm install --save reduxnpm install --save react-redux

Initialising your Store

Initialising your store can be done within your index.js file, or where you call ReactDOM.render() to your root ID. To start using Redux, we need to initialise the Store and use Redux’s <Provider /> component to make the Store available to the rest of the app. It looks like this:

  • Import the Provider from react-redux, and createStore from redux (yes, there are 2 separate packages)
  • Notice the third import, rootReducer — this is actually our index.js file that combined our child reducers (more on Reducers next). So we have effectively imported all reducers in one import statement
  • The second argument to createStore, initialStore, is optional. If you are preloading some data from your server or persisting state from cookies, then it makes sense to initialise your store with such data here

Actions and Reducers

If you have read the somewhat convoluted Redux introduction and Basics sections of their website, you would have run into the terms Actions and Reducers. Understanding these terms is important before jumping into some Redux code.

Actions — functions that return an object akin to a JSON object describing something to happen. There is one compulsory key, named type. We must give every action a type. Other arguments can be passed into an action that can be used to update your Store (state).

Reducers — functions that handle actions and update the store. We do this by returning the new state. Reducers must be pure functions.

Action Examples

Typical patterns to call actions on are onClick, or onSubmit events where you have submitted a valid form.

Let’s run through some examples of actions to drill in the concept:

// successful sign in
// username and authToken are passed into this action definition
export const setSignIn = (username, authToken) => (
{
type: 'SIGN_IN', //compulsory key!
user: username,
authToken: authToken
});

After a user has been authenticated, a SIGN_IN action can be called, where the authToken and username will be sent to the reducer and stored in state.

// signing out
// nothing is passed in, as I just need my reducer to delete
// my authentication and user data
export const setSignOut = () => (
{
type: 'SIGN_OUT'
});

A simple example of signing out of the app. This action will trigger my reducer to delete some credentials that are currently in state. Remember, your Store is read only, the only way to update it is via actions and reducers.

// toggle your progress bar
// our status here can be HIDE or SHOW
export const setProgressBar = (status) => (
{
type: 'SET_PROGRESS_BAR',
status: status
});

In the template files to follow I will give a Redux project structure and how to integrate actions into your project.

Reducer Examples

Reducers react to action calls (or dispatches) and handle them accordingly. The Redux docs encourage you to use switch statements to handle each action within your reducer. You can also define multiple reducers in separate files to keep your code organised. Reducers return the new, updated state.

The way reducers are organised in your project look like this:

src/
reducers/
index.js
ui.js
auth.js
...
  • In this setup, ui.js and auth.js would handle actions to do with UI changes and authentication. UI is admittedly quite vague, but would include things like handling your app’s progress bar. auth.js would handle a SIGN_IN action type or a SIGN_OUT action type — and let’s not forget checking valid auth tokens on a page refresh or revisit, an action for which could be named AUTHENTICATE_TOKEN.
  • index.js does not define any more reducers, and instead combines ui.js and auth.js using Redux’s combineReducers() function.

To demonstrate a reducer, reducers/ui.js may handle our progress bar action, like so:

// a reducer is just a switch statement that handles each action we define.// a reducer returns the new state, which in turn updates the Store.export const ui = (state = {}, action) => {   switch (action.type) {

case 'SET_PROGRESS_BAR':
return Object.assign({}, state, { progressBarStatus: action.status });


default:
return state;
}
};
export default ui

This reducer returns a new state with one change — the progressBarStatus updates to our action.status value.

Note: If our state is empty, we give it an initial value of an empty object, {}.

To include this reducer in our index (or root) reducers file, we do the following:

reducers/index.js:

import { combineReducers } from 'redux';
import ui from './ui';
import auth from './auth';
export default combineReducers({
ui,
auth,
});

We only have 2 reducers here, but as your application grows you can easily add more within combineReducers().

You can probably see what is happening here: On every action call, (or dispatch), the action type is passed through all our reducer files. If the action type is not matched within each switch statement, the default case just returns the current state.

Dispatch — the way to call Actions

dispatch() is another Redux function that we use to call actions. We can define an action dispatch like so:

const dispatchSetSignedIn = dispatch(setSignedIn(username, authToken));

We can now use dispatchSetSignedIn on an onClick event for example, which would in turn go through the Redux pipeline:

  1. setSignedIn returns our action definition in JSON format
  2. dispatchSetSignedIn will dispatch our action to our Reducers
  3. action.type is tested for each Reducer files’ switch statement. When the right one is found, it returns an updated state
  4. Our components connected to the updated state will re-render. Let’s go through how to do this in the last piece of the Redux puzzle

Connecting components to the Store

Connecting components to your Store is quite straight forward. To explain the standard way to code this, let’s refer to a component type ideology: Container and Presentational.

Concretely, a separate component is created to act as the Container to the component we wish to connect our Store to. The component we wish to connect state to then becomes our Presentational component.

  • Container Component: handles logic — the component we route URLs to. Nothing is rendered here.
  • Presentational Component: UI to the container. The Container component connects this component to it, therefore rendering this component.

The way state is passed to your presentational component is through props. Therefore we refer to this.props, and not this.state when getting our connected state data in the presentational.

How do you connect a presentational component to a container component?

Redux provide 2 utility functions we can utilise to pass props and action dispatches into our presentational component:

  1. mapStateToProps — connect state as props to the component (again, state from your Store is connected via props)
  2. mapDispatchToProps dispatching of actions as props, allowing us to call actions within the component.

These 2 utility functions are defined separately, followed by your Container component. Let’s briefly look at a template before a full example:

import { connect } from 'react-redux';

//Presentational component

class MyComponent extends Component {

render() {
...
}
}
//state: our state is passed as the first argument here
const mapStateToProps = (state, ownProps) => {
}
//actions: Redux's dispatch function is passed as the first argument here
const mapDispatchToProps = (dispatch, ownProps) => {
}
//defining the Container component, passing the above 2 functions into Redux's connect().
export const MyComponentContainer = connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
export default MyComponentContainer

So everything is getting passed down from the Container component to the Presentational component.

A full implementation of a Presentation and Container component is defined like so:

Take a look at the following points about this snippet of code:

  • ownProps is passed through both mapStateToProps and mapDispatchToProps, and is used to access props defined by the parent component of MyComponentContainer
  • Within mapDispatchToProps, props are being defined that call actions. We use dispatch() around the action to actually call it, in this case, when handleAction is called in the presentational
  • You are not obliged to define mapStateToProps or mapDispatchToProps. In the case you do not need either one, or both, replace them with null in your connect() . Do not mix the order: if you only wish to define mapDispatchToProps, place a null before it to maintain its second argument placement
  • MyComponent and MyContainerComponent can be named anything you wish. Using Handler after the Presentational component name is common, e.g. Login and LoginHandler, Home and HomeHandler. Another suggestion is to use Handle before the component name, e.g. HandleLogin and Login

Putting it all together

To put everything together, the remainder of this article provides you with some templates to inject directly into your project to get started with Redux.

You can copy and paste these snippets into your project as a starting point for getting a working Redux structure.

Create the following project structure for our Redux setup:

src/
actions/
index.js

components/
MyComponent.js

reducers/
index.js
ui.js

The following gist contains our initial Redux setup:

Great! If you have got this far, thank you reading this Redux introduction.

With the foundations securely under your belt, you should now have no issues jumping into the official Redux documentation to get all the ins-and-outs of how Redux works. Enjoy your Redux programming.

Ross Bulat

Written by

Director @ JKRB Investments

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade