DEV Community

aurel kurtula
aurel kurtula

Posted on

A beginner's introduction to working with redux in react

After covering the basics of redux in my other post, Introduction to Redux, the next logical step is to illustrate how react components connect to the redux store.

The key package that makes it possible for these two technologies to work together is the react-redux.

To easily get this project started, you should have create-react-app package globally installed, if you do not have that then quickly install it like so:

npm install -g create-react-app
Enter fullscreen mode Exit fullscreen mode

Then to create a fully functional starter app just use the above package like so:

create-react-app name-your-app-what-ever-you-like
Enter fullscreen mode Exit fullscreen mode

The benefits of starting this way is that all the boilerplate configuration - that have nothing to do with the actual react app but simply how it's run is already configured for us.

You would then start the app with npm start which will run your app in port 3000.

Having said all that if you never have worked with react, then this is not the right tutorial for you. Also, if you haven't played with redux before then I highly recommend going through my other tutorial on redux first.

Let's get started

The best way I learn new things is by using fewer files as possible. With that in mind, we are just going to use the src/index.js file. It's not the recommended way of working, for the obvious reason that modularity, braking the code in different files is one of the ways to keep the code clean and maintainable. But for our purpose this is better.

At the end of the tutorial I'll have links to the code we use in the index.js, plus I'll touch on how we can refactor the code to make it more modular.

Packages we'll need installed

Everything we need to work with react was installed by create-react-app command, all we need to install in addition are the following packages redux and react-redux. We can do so by running

npm i redux react-redux
Enter fullscreen mode Exit fullscreen mode

Or if you use yarn

yarn redux react-redux
Enter fullscreen mode Exit fullscreen mode

Whilst redux module doesn't need to be explained, react-redux is a module which makes the connection between react and redux.

As mentioned above, open src/index.js, delete what's on there and let's get started by importing our packages.

import ReactDOM from 'react-dom';
import React, { Component } from 'react';    
import { Provider, connect } from 'react-redux';
import { createStore, compose  } from 'redux';
Enter fullscreen mode Exit fullscreen mode

The first two were already installed by the app creator we ran above.

Working with redux

Reducers are functions that listen to the actions dispatched to redux and returns an immutable version of the store state.

When the app loads for the first time, the reducer is fired but there is no action, so it returns the initial state. We want the initial state to be an empty array (as specified as part of state argument, line one). Then if an action with the type of CREATE_TODO is fired, the reducer returns a new state adding the results of the action into the array.

const todos =  (state = [], action) => {
  switch (action.type) {
    case 'CREATE_TODO':
    return state.concat([action.text])
    default:
      return state;
  }
};
Enter fullscreen mode Exit fullscreen mode

Next, let's create the store by using the createStore method provided by redux. It accepts three possible arguments: a reducer, the preloadedState, and enhancers(these are explained in redux documentation), only the first argument is a must.

const store = createStore(
  todos,
  undefined,
  compose(
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )
)
Enter fullscreen mode Exit fullscreen mode

Since we have already specified the state in the reducer, we set the second argument to undefined, however, if you set it to an empty array or to, say, ['item one'] it would simply mean the reducer would use it as the initial state (great for preloading data that you might retrieve from an API).

The enhancer we used (third argument) simply allows our app to interact with redux browser extension (if you don't have in installed you can get more information here). It has no effect on the actual app, it's simply a tool to help you as a developer.

Using the redux store in in react components

A very basic react setup would look like this:

class App extends Component {
  render() {
    return (
      <h1>Hello world</h1>
    );
  }
}
ReactDOM.render(
    <App />, 
  document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

A react component which returns Hello World is rendered on the page, inside an element with the id of root.

As it stands, our react component isn't aware of the redux store.

To make the connection we have to utilise the react-redux module which gives us two additional components, Provider and connect(), both of which allow react to interact with redux.

As the names suggests, Provider provides the store to our entire react application and connect enables each react component to connect to the provided store.

Remember, we have already imported these two methods in our src/index.js file.

import { Provider, connect } from 'react-redux';
Enter fullscreen mode Exit fullscreen mode

From the react-redux documentation we learn that:

<Provider store> Makes the Redux store available to the connect() calls in the component hierarchy

So let's do that. Let's make the Redux store available to connect() and in turn give our react component access to the store.

class App extends Component {
  //...
}
const MyApp = connect( state => ({
    todos: state
  }), { createTodo })(App);

ReactDOM.render(
  <Provider store={store}>
    <MyApp />
  </Provider>, 
  document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

MyApp is our App component with the added benefit of having the store and actions injected in its state.

Again, Provider gets the store and passes it to connect() and connect() passes it to the react component.

What is connect() really doing?

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

[connect() is] A higher-order React component class that passes state and action creators into your component derived from the supplied arguments. - From documentation

const MyApp = connect( state => ({
    todos: state
  }), { createTodo })(App);
Enter fullscreen mode Exit fullscreen mode

First argument, mapStateToProps, gets the state (which is made available by the Provider) assigns a variable name todos and passes it into the props of our component.

The next argument, [mapDispatchToProps], passes our createTodo action to the component props as well. The createTodo is a function that returns the object that reducers listen for.

const createTodo = (text)=>{
  return {
    type: 'CREATE_TODO',
    text
  }
}
Enter fullscreen mode Exit fullscreen mode

(Again, we covered those in the previous Introduction to Redux tutorial)

Working with the react component

Now we have access to the redux store state from the react App component. Let's finally interact with the store from there.

class App extends Component {
  _handleChange = e => {
    e.preventDefault()
    let item = e.target.querySelector('input').value;
    this.props.createTodo(item);
  }
  render() {
    return (
      <div>
      <form onSubmit={this._handleChange}>
        <input type="text" name="listItem" />
       <button type="submit">button</button>
       </form>
        <br />
        {this.props.todos.map((text, id) => (
          <div key={id}>
              {text}
          </div>
        )) }
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Focus on the render() method first. We return a form. Upon submit _handleChange method is triggered. From there the createTodo action is dispatched.

Further down, we loop through the todos array (which we constructed in connect() component) and render them on the page.

Note: whenever we loop through a list to render the value, react requires us to provide a unique key, otherwise we get a warning of: Warning: Each child in an array or iterator should have a unique "key" prop. The documentation explains why react requires unique keys to be passed to each element:

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity

And that's it.

We've created a simple todo list where we can add items to redux store and display them back, from the redux store to the react component.

Between this tutorial and the Introduction to Redux you could build upon this to add other functionalities such as delete, archive and edit. All the heavy lifting for this extra functionality would go into redux reducers and actions. In the react App component only few buttons to trigger the extra actions would need to be added.

Going modular

All the code we covered so far goes into one file, the src/index.js. I made the file available here

In a proper application this code would be modularised into separate files. Here is one way to do that. The code is the same, we simply take advantage of import/export features of ES6:

In src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import MyApp from './App';
ReactDOM.render(
  <Provider store={store}>
    <MyApp />
  </Provider>, 
  document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

In src/App.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createTodo } from './actions/todos';
class App extends Component {
    // exact same code
}
export default connect( state => ({
    todos: state.todos
  }), { createTodo })(App);
Enter fullscreen mode Exit fullscreen mode

In src/actions/todos.js

This is where all the actions such as deleteTodo would go, but we only had one:

export function createTodo(text){
  return {
    type: 'CREATE_TODO',
    text
  }
}
Enter fullscreen mode Exit fullscreen mode

In src/store.js

import { combineReducers } from "redux"; 
import { createStore, compose } from 'redux';   
import todos from './reducers';
const  rootReducer =  combineReducers({
  todos
})
export default createStore(
  rootReducer,
  undefined,
  compose(
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )
)
Enter fullscreen mode Exit fullscreen mode

In src/reducers/index.js

If we had more than one reducer, we would utilise the combineReducers module as we did in the Introduction to Redux tutorial, but as it stands now, we just transfer our one reducer here, like so:

export default (state = [], action) => {
  switch (action.type) {
    case 'CREATE_TODO':
    return state.concat([action.text])
    default:
      return state;
  }
};
Enter fullscreen mode Exit fullscreen mode

Thanks for reading!

Top comments (1)

Collapse
 
amit_merchant profile image
Amit Merchant

Thanks for this comprehensive tutorial!