DEV Community

loading...
Cover image for Setting Up Redux in React

Setting Up Redux in React

Kelvin Mwinuka
Software Engineer primarily interested in web applications and APIs. Favourite languages are Python and Javascript
Originally published at kelvinmwinuka.com ・14 min read

Setting up redux in your react application can be confusing, but it doesn't have to be. In this article, I walk you through the entire process of setting up and connecting redux to your react application along with a practical application example.

This article is divided into the following sections:

Creating the application - building our example application.

Installing the packages

Action creators - A brief explanation and example of action creators.

Reducer - A brief explanation and example of a reducer.

Connecting the app - Connecting the application to the redux store.

Dispatching actions

Enhancement with redux-thunk - Enhancing the development experience by applying redux-thunk middleware

I'd recommend that you follow along from the beginning as we use an app example, but feel free to skip ahead to a particular section if you feel comfortable with certain concepts discussed here.

1. Creating the application

In this example, we will create a simple application that displays a user's profile containing a name, bio and 2 lists: one for programming languages and one for frameworks.

Let's create the app using create-react-app:

npx create-react-app <app_name>
Enter fullscreen mode Exit fullscreen mode

Let's add an image in the public folder to be used as the profile picture(optional) and create a component file in /src named TechList.js.

Our folder structure should look like this:

.
├── README.md
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   ├── pro.jpg
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── TechList.js
    ├── index.js
    ├── logo.svg
    ├── serviceWorker.js
    └── setupTests.js
Enter fullscreen mode Exit fullscreen mode

Let's define the App component:

import React from 'react';
import TechList from './TechList'
import './App.css';

const App = () => {
  return (
    <div className="App">
      <div className="media">
        <img className="align-self-start mr-3 profile-pic" src="pro.jpg" alt="Profile" />
        <div className="media-body">
          <h5 className="mt-0">{/** Bio will go here */}</h5>
          <p>{/** Bio will go here */}</p>
          <div className="container tech-container">
            <div className="row">
              <div className="col-sm">
                {/** Programming lanugages list */}
                <TechList 
                  items={[]}
                />
              </div>
              <div className="col-sm">
                {/** Programming lanugages list */}
                <TechList 
                  items={[]}
                />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Now let's define the TechList component, a reusable component that will display both the languages and frameworks lists:

import React from 'react'

const TechList = ({
    items,
}) => {

    const handleFormubmit = (event) => {
        event.preventDefault()
        event.target.reset()
    }
    return (
        <ul className="list-group">
            {
                items.map( (item, index) => {
                    return <li key={index} className="list-group-item">{item}</li>
                })
            }
            <li className="list-group-item">
                <form onSubmit={handleFormubmit}>
                    <div className="form-row">
                        <div className="col">
                            <input type="text" className="form-control add-tech-text" placeholder="Type new" name="entry" required/>
                        </div>
                        <div className="col">
                            <button type="submit" className="btn btn-primary">Add to list</button>
                        </div>
                    </div>
                </form>
            </li>
        </ul>
    )
}

export default TechList
Enter fullscreen mode Exit fullscreen mode

This component receives an items prop which is an array containing the languages/frameworks that will be displayed in the list. At the moment, we're passing an empty array from the App component so this will not display anything.

It also contains a form appended at the end of the list that allows us to enter some text to be appended to the list dynamically. We will add functionality to this later.

Next, let's set up a redux folder inside /src that will contain our action creators and reducer. Inside the folder, we'll have actions.js and reducer.js. The folder structure should now look like this:

.
├── README.md
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   ├── pro.jpg
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── TechList.js
    ├── index.js
    ├── logo.svg
    ├── redux
    │   ├── actions.js
    │   └── reducer.js
    ├── serviceWorker.js
    └── setupTests.js
Enter fullscreen mode Exit fullscreen mode

2. Installing the packages

We will need to install the necessary packages with the following command:

npm install redux react-redux redux-thunk axios
Enter fullscreen mode Exit fullscreen mode

3. Action creators

Our action creators will be located inside the actions.js file. We will have 2 action creators for now: one that creates an action that sends data to add a programming language to the store, and one that sends data to add a framework.

Our code in actions.js will look like this:

export const addLanguange = (language) => {
    return {
        type: 'ADD_LANGUAGE',
        payload: language
    }
}

export const addFramework = (framework) => {
    return {
        type: 'ADD_FRAMEWORK',
        payload: framework
    }
}
Enter fullscreen mode Exit fullscreen mode
<!-- wp:heading {"level":3} -->
<h3 id="reducer">4. Reducer</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Our reducer.js file will contain our reducer:</p>
<!-- /wp:paragraph -->
Enter fullscreen mode Exit fullscreen mode
const initial_state = {
    profile: {
        name: 'Kelvin Clement Mwinuka',
        bio: 'I am a software developer with a BS in Computer Science from The University of Nottingham. I’m passionate about web technologies. On my free time, I like blogging and challenging myself physically.',
        languages: [
            'JavaScript', 'Python', 'HTML', 'CSS'
        ],
        frameworks: [
            'React', 'Express', 'Flask', 'Django'
        ]
    },
}

const rootReducer = (state = initial_state, action) => {
    switch (action.type) {
        case 'ADD_LANGUAGE':
            return {
                ...state, 
                profile: {
                    ...state.profile,
                    languages: [...state.profile.languages, action.payload]
                }
            }
        case 'ADD_FRAMEWORK':
            return {
                ...state, 
                profile: {
                    ...state.profile,
                    frameworks: [...state.profile.frameworks, action.payload]
                }
            }
        default:
            return state
    }
}

export default rootReducer
Enter fullscreen mode Exit fullscreen mode

In this example, I've set up an initial state with some pre-loaded values. When an action is dispatched, the reducer will figure out which part of the state to append data.

Keep the reducer pure by not having any other logic besides returning the new state. We also should not directly mutate the state.

5. Connecting the app

Now that we have our action creators and reducer, it's time to connect our application to redux so we can actually use them.

Let's open the index.js file and make the following changes:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import rootReducer from './redux/reducer'

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
)

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
Enter fullscreen mode Exit fullscreen mode

First, we import createStore and applyMiddleware. createStore is exactly what it sounds like: it allows us to create the store that will hold our data. applyMiddleware allows us to extend the functionality of redux by adding packages called middleware.

Next, we import the Provider component from react-redux that will wrap our App component.

Our third import is a middleware package called redux-thunk, I will get into more detail about this in section 7 (Enhancement with redux-thunk).

The final import is our reducer. We only have one to import here. However, if you have multiple reducers, you could merge them into one giant reducer using combineReducer from the redux package.

Now we can create our store using createStore and pass in our reducer, and then applying the middleware.

If you wish to stop here or if this simple setup is sufficient, you do not have to use applyMiddleware at all. You could just pass the reducer and call it a day. I've added the middleware here in order to set up for the redux-thunk section.

Now lets go into our App component in App.js and make the following changes:

import React from 'react';
import TechList from './TechList'
import { bindActionCreators } from 'redux'
import { addLanguange, addFramework } from './redux/actions'
import { connect } from 'react-redux'
import './App.css';

const App = ({
  profile,
  action
}) => {
  return (
    <div className="App">
      <div className="media">
        <img className="align-self-start mr-3 profile-pic" src="pro.jpg" alt="Profile" />
        <div className="media-body">
          <h5 className="mt-0">{profile.name}</h5>
          <p>{profile.bio}</p>
          <div className="container tech-container">
            <div className="row">
              <div className="col-sm">
                {/** Programming lanugages list */}
                <TechList 
                  items={profile.languages}
                  action={actions.addLanguange}
                />
              </div>
              <div className="col-sm">
                {/** Programming lanugages list */}
                <TechList 
                 items={profile.languages}
                  action={actions.addFrameworks}
                />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function mapStateToProps (state) {
  return {
    profile: state.profile
  }
}

function mapDispatchToProps (dispatch) {
  return {
    actions: bindActionCreators({ 
      addLanguange,
      addFramework 
    }, dispatch)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App);
Enter fullscreen mode Exit fullscreen mode

First, we import bindActionCreators from redux which allows us to combine all our action creators into one object with corresponding keys.

This is not necessary but I find this to be a clean way of dealing with action creators especially as the project grows and necessitates the usage of more of action creators.

Next we import our action creators themselves from actions.js.

Finally, we import connect from 'react-redux'. This allows us to connect a particular component to our store. We will have this only on the App component and pass down any action creators or data as props.

If you have a large project, you could use this method on multiple components especially if you want to make sure you're only subscribing to certain parts of the state rather than the entire store.

We've added a mapStateToProps function:

function mapStateToProps (state) {
  return {
    profile: state.profile
  }
}
Enter fullscreen mode Exit fullscreen mode

This takes the state contained in our redux store as a parameter and returns an object that can be considered a subset of the state. The object in question will be passed to the current component via its props.

Right now, we are subscribing to the 'profile' object in the state. Meaning the component will only re-render if this section of the state changes.

This is one of the strengths of redux. The component does not have to re-render if the portion of the state it subscribes to has not changed. Even if the state has changed elsewhere.

If we end up expanding our state in reducer.js and add another section other than 'profile', the App component and subsequently, its children, will not re-render if the new portion of the state changes.

We've also added another function:

function mapDispatchToProps (dispatch) {
  return {
    actions: bindActionCreators({ 
      addLanguange,
      addFramework 
    }, dispatch)
  }
}
Enter fullscreen mode Exit fullscreen mode

This function enables us to fire our action creators within the app component and its children provided they are passed down.

We make the following update on both instances of the TechList component:

              ...
               {/** Programming lanugages list */}
               <TechList 
                  items={profile.languages}
                  action={actions.addLanguange}
                />
              ...
                {/** Programming lanugages list */}
                <TechList 
                 items={profile.languages}
                  action={actions.addFrameworks}
                />
Enter fullscreen mode Exit fullscreen mode

We pass the relevant items list and action creator to each of the instances.

6. Dispatching actions

Now that we've connected the application to the redux store, let's dispatch the actions.

The actions in question add a programming language and a framework to the state's languages and framework lists respectively. In order to make this possible, we'll update the TechList component to the following:

import React from 'react'

const TechList = ({
    items,
    action
}) => {

    const handleFormubmit = (event) => {
        event.preventDefault()
        action(event.target.entry.value)
        event.target.reset()
    }
    return (
        <ul className="list-group">
            {
                items.map( (item, index) => {
                    return <li key={index} className="list-group-item">{item}</li>
                })
            }
            <li className="list-group-item">
                <form onSubmit={handleFormubmit}>
                    <div className="form-row">
                        <div className="col">
                            <input type="text" className="form-control add-tech-text" placeholder="Type new" name="entry" required/>
                        </div>
                        <div className="col">
                            <button type="submit" className="btn btn-primary">Add to list</button>
                        </div>
                    </div>
                </form>
            </li>
        </ul>
    )
}

export default TechList
Enter fullscreen mode Exit fullscreen mode

This component takes an items props which it loops through and displays in a list as described before. The second prop is an actions prop. This will contain an action creator that will be invoked and passed the data grabbed from the form submission.

This component is action creator agnostic, even though it is the one invoking the action creator. So it's important to pass the correct action creator down from the parent component.

Congratulations! you've connected your app to redux. Now you can add new items to each of the lists.

Next, we'll take a look at how to enhance this app. At the moment, action creators can only return an action object. This is great if we already have the data we want to return.

What about a situation where we need to retrieve data from a server through an API call? We can't do this in the reducer as it needs to be pure. The action creator is the place to do this. We need a way to add this logic here. This is where redux-thunk comes in.

7. Enhancement with redux-thunk

To understand redux-thunk, we first need to understand what a thunk is. A thunk is a function that delays the execution of some code until the exact moment the result of that execution is needed. In our case, that code is dispatching an action.

Why is this important? At the moment, we have to dispatch an action that consists of the type and the payload. This requires that we already have the payload data beforehand.

What if we don't have that data? What if we need to retrieve that data from a server before we display it? This is what a thunk is useful for. In this case, instead of dispatching an action directly, we want to make a request to the server and then dispatch an action with the data from the response.

Our action creators need to return a function that has this logic and then returns an action at the end of its execution. This is the thunk.

In order to enable thunks in redux, we need to apply the redux-thunk middleware, which we have already done.

First, let's write. a simple Node server that listens on port 8000 for requests. This server has a '/profile' GET endpoint which returns the user's profile details, a '/languages' POST endpoint that adds to the user's list of languages, and a '/frameworks' POST endpoint that adds to the user's list of frameworks.

Each endpoint returns the latest user object as a JSON response.

var bodyParser = require('body-parser')
var cors = require('cors')
var app = require('express')()

const port = 8000

var profile = {
    name: 'Kelvin Mwinuka',
    bio: 'I am a software developer with a BS in Computer Science from The University of Nottingham. I’m passionate about web technologies. On my free time, I like blogging and challenging myself physically.',
    languages: [],
    frameworks: []
}

app.use(cors())
app.use(bodyParser.json())

app.post('/languages', (req, res) => {
    let language = req.body.language
    if (!profile.languages.map( l => l.toLowerCase()).includes(language.toLowerCase())) {
        profile.languages.push(language)
    }
    res.json(profile)
});

app.post('/frameworks', (req, res) => {
    let framework = req.body.framework
    if (!profile.frameworks.map( f => f.toLowerCase()).includes(framework.toLowerCase())) {
        profile.frameworks.push(framework)
    }
    res.json(profile)
});

app.get('/profile', (req, res) => {
    res.json(profile)
});

http.listen(port, () => {
    console.log(`Server started at port ${port}`)
});
Enter fullscreen mode Exit fullscreen mode

Let's make the necessary changes in actions.js to enable the desired behaviour:

import axios from 'axios'

export const setProfileData = (profile) => {
    return {
        type: 'SET_PROFILE_DATA',
        payload: profile
    }
}

export const loadProfile = () => {
    return async (dispatch) => {
        let res = await axios.get('http://localhost:8000/profile')
        let profile = res.data
        dispatch(setProfileData(profile))
    }
}

export const addLanguange = (language) => {
    return async (dispatch) => {
        let res = await axios.post('http://localhost:8000/languages', { 
            language: language 
        })
        let profile = res.data
        dispatch(setProfileData(profile))
    }
}

export const addFramework = (framework) => {
    return async (dispatch) => {
        let res = await axios.post('http://localhost:8000/frameworks', { 
            framework: framework 
        })
        let profile = res.data
        dispatch(setProfileData(profile))
    }
}
Enter fullscreen mode Exit fullscreen mode

The first change we've made is the addition of a 'setProfileData' action creator that behaves like a regular action creator (no thunk) to set the profile data if we already have it.

Notice what we've done with the 'addLanguage' and 'addFramework' action creators? Instead of returning a raw action object, we instead return an async function that takes dispatch as a parameter.

This function executes whatever logic is needed first, and only then will it dispatch an action. This is what a thunk is. A thunk can also be used for conditional dispatches but that is outside the scope of this article.

We've also added another action creator called 'loadProfile' that is explicitly responsible for retrieving the user profile from the server. It behaves similar to the 'addLanguage' and 'addFramework' action creators.

Another important thing to note is that these 3 action creators now pass the 'setProfileData' action creator to the dispatch function. We can do this because that action creator returns a raw action. Therefore, it's equivalent to passing the action object directly to dispatch. I take this approach in order to avoid typing the same action object multiple times.

In the reducer, let's add one more case for setting the user profile. The data is no longer hardcoded in the initial state and will instead be set by dispatching an action after retrieving it from the server.

const initial_state = {
    profile: {
        name: '',
        bio: '',
        languages: [],
        frameworks: []
    },
}

const rootReducer = (state = initial_state, action) => {
    switch (action.type) {

        case 'SET_PROFILE_DATA':
            return {...state, profile: action.payload}

        case 'ADD_LANGUAGE':
            return {
                ...state, 
                profile: {
                    ...state.profile,
                    languages: [...state.profile.languages, action.payload]
                }
            }
        case 'ADD_FRAMEWORK':
            return {
                ...state, 
                profile: {
                    ...state.profile,
                    frameworks: [...state.profile.frameworks, action.payload]
                }
            }
        default:
            return state
    }
}

export default rootReducer
Enter fullscreen mode Exit fullscreen mode

In the app section, let's import our new 'loadProfile' action creator and then invoke it right at the top of our app component in order to trigger the user profile retrieval from the server.

import React from 'react';
import TechList from './TechList'
import { bindActionCreators } from 'redux'
import { addLanguange, addFramework, loadProfile } from './redux/actions'
import { connect } from 'react-redux'
import './App.css';

const App = ({
  profile,
  actions
}) => {

  actions.loadProfile()

  return (
    <div className="App">
      <div className="media">
        <img className="align-self-start mr-3 profile-pic" src="pro.jpg" alt="Profile" />
        <div className="media-body">
          <h5 className="mt-0">{profile.name}</h5>
          <p>{profile.bio}</p>
          <div className="container tech-container">
            <div className="row">
              <div className="col-sm">
                {/** Programming lanugages list */}
                <TechList 
                  items={profile.languages}
                  action={actions.addLanguange}
                />
              </div>
              <div className="col-sm">
                {/** Programming lanugages list */}
                <TechList 
                  items={profile.frameworks}
                  action={actions.addFramework}
                />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function mapStateToProps (state) {
  return {
    profile: state.profile
  }
}

function mapDispatchToProps (dispatch) {
  return {
    actions: bindActionCreators({ 
      loadProfile,
      addLanguange,
      addFramework
    }, dispatch)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App);
Enter fullscreen mode Exit fullscreen mode

That's it! run the app and you'll notice that we've retained all the functionality we had before from the user's perspective, but we can now create smarter action creators that allow us to accomplish more with redux.

The post Setting Up Redux in React appeared first on Kelvin Mwinuka.

If you enjoyed this article, consider following my website for early access to my content before it gets published here (don’t worry, it’s still free with no annoying pop-up ads!). Also, feel free to comment on this post. I’d love to hear your thoughts!

Discussion (2)

Collapse
markerikson profile image
Mark Erikson

Hi, I'm a Redux maintainer. The patterns shown in this post are actually very outdated and we recommend against doing things this way. in 2021, no one should be writing nested spread operations for immutable updates or having to add the thunk middleware to the store separately, and while connect still works fine, we suggest that you not use it.

"Modern Redux" code is very different than what most older tutorials show. We've introduced newer APIs like our official Redux Toolkit package , 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:

  • "Redux Essentials" tutorial: teaches "how to use Redux, the right way", by building a real-world app using Redux Toolkit
  • "Redux Fundamentals" tutorial: teaches "how Redux works, from the bottom up", by showing how to write Redux code by hand and why standard usage patterns exist, and how Redux Toolkit simplifies those patterns

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.

Collapse
kelvinvmwinuka profile image
Kelvin Mwinuka Author

Thanks for the feedback Mark. I'll be sure to get updated on the latest recommended redux practices