paint-brush
How to Refactor Your React Application And Connect with Reduxby@rajasekhar-k
1,654 reads
1,654 reads

How to Refactor Your React Application And Connect with Redux

by Rajasekhar KJuly 14th, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

How to Refactor Your React Application And Connect with Redux with Redux? How to create a React app that has minimal functionality of creating and deleting a list of books. This app is similar to a much-publicized ‘todo’ app by renaming the component and variable names. Redux is a predictable state container for JavaScript apps - as stated in Redux official documentation. You can use it to create Todo app also by changing the name of the app to react-redux.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - How to Refactor Your React Application And Connect with Redux
Rajasekhar K HackerNoon profile picture
  • Introduction
  • Create a simple React App
  • Migrating the app to Redux
  • Flow of Data in a Redux App
  • The Redux App - explanation
  • Conclusion 

Introduction 

Redux is a predictable state container for JavaScript apps - as stated in Redux official documentation.  When I started learning Redux, I found it difficult to comprehend the concept and implementation. I skimmed through many articles on the net but could not find a satisfactory answer. Most of the articles either explain creating apps with Redux or converting React apps with no basic code to Redux.  I could not find something that explained how a working React app can be modified to use Redux with the same functionality.  

In this article, I have made an attempt to do exactly that.  I will first explain how to create a React app that has minimal functionality of creating and deleting a list of books.  This app is similar to a much-publicized ‘todo’ app.  You can use it to create a Todo app also by just renaming the component and variable names.

Create a simple React App

With this introduction, let us dive into hands-on.  Open your code editor and type ‘npx create-react-app react-redux’.  You may change the name of the app from react-redux to whatever you like. Once the app creation process completes, run ‘npm start’ at your console and confirm that the app is working fine with a gently rotating React logo.

Now delete all the files except those shown in the list below and create blank files which are needed:

Create a directory ‘components’ under ‘src’ directory and create three blank files Book.js, BookForm.js, and BookList.js under the ‘components’ directory.  Once you have the files and folders, as shown above, replace the contents of the files App.js and Index.js from the code given below, and fill the other three blank files with the code given below :

index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
 
ReactDOM.render(
  <App />,
  document.getElementById("root")
);
 

App.js

import React from "react";
import BookForm from "./components/BookForm";
import BookList from "./components/BookList";
 
class App extends React.Component {
  state = {
    books: []
  };
 
  addBook = book => {
    this.setState(state => ({
      books: [book, ...state.books]
    }));
  };
  removeBook = id => {
    this.setState({
      books: this.state.books.filter(book => book.id !== id)
    });
  };
 
  render() {
    return (
      <div>
        <h1> React to Redux</h1>
        <BookForm onSubmit={this.addBook} />
        <BookList books={this.state.books} removeBook={this.removeBook} />
      </div>
    );
  }
}
 
export default App;

Book.js

import React from "react";
 
const Book = props => {
  const { book } = props;
  return (
    <div>
      <div>
        {book.title} <button onClick={props.onDelete}>Delete</button>
      </div>
    </div>
  );
};
 
export default Book;

BookForm.js

import React from "react";
 
class BookForm extends React.Component {
  constructor() {
    super();
    this.state = { title: "" };
 
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
 
  handleChange(event) {
    const { name, value } = event.target;
    this.setState({
      [name]: value
    });
  }
 
  handleSubmit(event) {
    event.preventDefault();
    if (this.state.title)
      this.props.onSubmit({
        id: Date.now(),
        title: this.state.title
      });
    this.setState({
      title: ""
    });
  }
 
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type="text"
          value={this.state.title}
          name="title"
          placeholder="Book Title"
          onChange={this.handleChange}
        />
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
 
export default BookForm;

BookList.js

import React from "react";
import Book from "./Book";
 
const BookList = props => {
  return (
    <div>
      {props.books.map(book => (
        <ul key={book.id}>
          <Book book={book} onDelete={() => props.removeBook(book.id)} />
        </ul>
      ))}
    </div>
  );
};
 
export default BookList;

Migrating the app to Redux

Now that we have created a React app that displays a form, accepts books, displays the list and deletes any, we have a fully functional React app. Next, we will refactor the same app and see how we can use Redux to maintain the data of the app.  Please remember, the purpose of this article is not to teach React but to teach how to understand and use Redux in a React application.

Before proceeding further, please remember we use Redux to maintains the ‘state’ or ‘data’ of a React application.  In this article we are going to do exactly that, moving the state of React app into Redux.

Flow of data in a Redux App

We shall try to understand the basics of Redux from the diagram above. On top right there is a box ‘React App’ in which the data flows from top component to bottom and it is held in ‘state’ of the root component.  

If you look at the bottom right diagram which shows ‘Redux App’, the data is not stored in the components but it is stored in a single place called ‘Store’.  The data received by the components from the external interface is sent to Store using ‘mapDispatchToProps’, and the ‘connect’ function connects the component sending the data and the Store.

The data being sent to the ‘Store’ passes through two stages viz.

Actions’ and ‘Reducers’. The ‘Actions’ is an object that specifies what type of action is needed to be done on the data and what are the other parameters for that action. Then this data is passed on to ‘Reducer’. Reducer is a function which performs the required action, i.e. either storing the data in the required format or creating a new set of data and replacing the old data  or fetching data from the Store. This is how data is modified because in React state is not mutated.  

Now we shall briefly look at the new terms used above. The store has the following responsibilities:

But these things are handled internally and we will not generally use those methods explicitly. 

Store is the place within the app where the data is stored.  It is similar to ‘State’ in React Apps. Store is not accessible to the components of the App directly. Components have to connect to the Store making use of two methods.

The first one is mapDispatchToProps which sends data from the components to Store and the second one is mapStateToProps which receives data from the Store into the component as prop. But these two functions need connect() which connects Store and components. When you use connect, it is followed by the name of the component being connected in parentheses.

There are two more things, viz. Action and Reducer.  Action is an object which specifies the inputs needed to an operation of fetching or sending data to Store. Reducer is a method which uses Action details and performs the required operation on the Store.

The components of the app are segregated into ‘components’ and ‘containers’ folders. We keep presentation components i.e. components that have a user interface under components folder and the components that deal with the Store are kept under containers folder. In addition to these, we create a folder called ‘actions’ and the action objects are placed in this folder. 

We also create ‘reducers’ folder and keep the reducer objects under this folder. This is done so that Redux can pick up the needed components from these locations without specifying where to look for them.

The Redux App 

Before we start refactoring our code, it is advised to make a copy of the working React app you have created just now.

To begin with, let us add redux to our app by running the following commands from the terminal:

 npm install redux              
 npm install react-redux

First, we segregate our components depending on the functions they do and keep them separately in separate folders.  For this purpose, let us create four folders with the following names. I am also specifying the file names which go under each of these folders. Only index.js is kept in the root folder.

index.js
components
	App.js
	Book.js
containers
	BookForm.js
	BookList.js
actions
	index.js
reducers
	book.js
	index.js

Now move App.js and Book.js to the folder Components and move BookForm.js and BookList.js to the containers folder. We don't have any files under actions and reducers folders which we shall be adding shortly.

Follow the code below and wherever it is commented '//', delete those lines and wherever it is mentioned '//add' at the end of the line, add those lines to your React App.  Alternatively, you may copy the entire React App code or Redux App code from my GitHub, the link for which is given at the end of this article.  I shall give a brief explanation of the changes we are making to each of the files after the code listing of each file

index.js

import React from "react";
import ReactDOM from "react-dom";
// import App from “./App”;
Import App from “./components/App";		//add
import { createStore } from "redux";		//add
import { Provider } from "react-redux";	//add
import reducer from "./reducers";		//add

const store = createStore(reducer);		//add

ReactDOM.render(
<Provider store={store}>			//add
  <App />			// remove ‘,’
 </Provider>,					//add
  document.getElementById("root")
);

In the above file, we can see that the location of the ‘App’ component is moved from the root folder to ‘components’ folder. Then we imported two Redux components ‘createStore’ and ‘Provider’. CreateStore initializes the Store. The <Provider /> makes the Redux store available to any nested components that have been wrapped in the connect() function. The ‘reducer’ is going to be defined in a separate file which is being imported here.

actions/index.js

export const addBook = title => ({
  type: "ADD_BOOK",
  id: Date.now(),
  title
});

export const removeBook = id => ({
  type: "REMOVE_BOOK",
  id
});

The index.js file under actions folder defines the various actions that are required in the app. Each action is defined by providing some attributes like ‘type of the action’ and the inputs needed to perform that action.  For our app, we have defined two action objects ‘ADD_BOOK’ and ‘REMOVE_BOOK’  giving other information like ‘id’ and ‘title’. 

These action objects would be used by the reducer methods defined in the files under the ‘reducer’ folder. 

reducers/books.js

const books = (state = [], action) => {
  switch (action.type) {
    case "ADD_BOOK":
      return [
        ...state,
        {
          id: action.id,
          title: action.title
        }
      ];
    case "REMOVE_BOOK":
      return state.filter(book => book.id !== action.id);
    default:
      return state;
  }
};

export default books;

The file ‘books.js’ defines the methods ‘ADD_BOOK’ and ‘REMOVE_BOOK’ which add and remove books from the store.  We can have more than one file defining the reducers for various actions required in the app, but then we need to use another reducer ‘index.js’ which combines them into one. For demo purposes, I have also defined ‘index.js’ which combines other reducers though we have only one other reducer, i.e. books.js.  

reducers/index.js

import { combineReducers } from "redux";
import books from "./books";

export default combineReducers({
  books
});

The file ‘index.js’ under the ‘reducers’ folder combines other reducers defined in other files, into one, and makes it available to the store. 

So far, we have added the above three files and are going to  modify other files as discussed below.

components/App.js

import React from "react";
// import BookForm from "./components/BookForm";
// import BookList from "./components/BookList";
import BookForm from "../containers/BookForm"; 	//add
import BookList from "../containers/BookList";	//add

class App extends React.Component {
 // state = {
 //   books: []
//  };

//  addBook = book => {
//    this.setState(state => ({
//      books: [book, ...state.books]
//    }));
//  };

//  removeBook = id => {
//    this.setState({
//      books: this.state.books.filter(book => book.id !== id)
//    });
//  };

  render() {
    return (
      <div>
        <h1> React to Redux</h1>
        <BookForm onSubmit={this.addBook} />
        <BookList books={this.state.books} removeBook={this.removeBook} />
      </div>
    );
  }
}

export default App;

In the App.js file above we changed the top two lines just because we have changed the locations of those files.  Then we have removed ‘declaring the state’, and methods for ‘adding’ and ‘deleting’ the books.  Because in Redux, state is maintained by Store and add and delete methods are handled by the reducers defined in ‘reducers/books.js’ file.

components/Book.js

import React from "react";

const Book = props => {
//  const { book } = props;
const { book, removeBook } = props;				//add
  const handleRemoveBook = () => removeBook(book.id);	//add

  return (
    <div>
      <div>
//        {book.title} <button onClick={props.onDelete}>Delete</button>
  {book.title} <button onClick={handleRemoveBook}>Delete</button>//add

      </div>
    </div>
  );
};

export default Book;

In the above file ‘Book.js’ there is not much change except for the syntax

containers/BookForm.js

import React from "react";
import { connect } from "react-redux";		//add
import { addBook } from "../actions";		//add

class BookForm extends React.Component {
  constructor() {
    super();
    this.state = { title: "" };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    const { name, value } = event.target;
    this.setState({
      [name]: value
    });
  }

  handleSubmit(event) {
    event.preventDefault();
//    this.props.onSubmit({
//      id: Date.now(),
//      title: this.state.title
//    });
const { title } = this.state;			//add
const { addBook } = this.props;			//add

    if (title) {						//add
      addBook(title);					//add

    this.setState({
      title: ""
    });
  }
}
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type="text"
          value={this.state.title}
          name="title"
          placeholder="Book Title"
          onChange={this.handleChange}
        />
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

// export default BookForm
export default connect(null, { addBook })(BookForm);	/add

In this file, we are importing ‘connect’ from ‘react-redux’ and ‘addBook’ from actions/index.js. Accordingly, we are deleting the OnSubmit method and in its place using the ‘addBook’ method which is defined in actions/books.js file. We are passing the ‘title’ which was initially stored in the ‘state’ of the component, to ‘addBook’ and passing this ‘addBook’  as 'mapDispatchToState' method which was used by the ‘connect’ function that is connecting the Store and BookForm components.

containers/BookList.js

import React from "react";
// import Book from "./Book";
import Book from "../components/Book";				//add
import { connect } from "react-redux";				//add
import { removeBook } from "../actions";			//add

function mapStateToProps(state) {				//add
  const { books } = state;						//add
  return { books };							//add
}										//add

const mapDispatchToProps = dispatch => ({			//add
  removeBook: id => dispatch(removeBook(id))		//add
});										//add

// const BookList = props => {
//  return (
//    <div>
//      {props.books.map(book => (
//        <ul key={book.id.toString()}>
//          <Book book={book} onDelete={() => props.removeBook(book.id)} />
//        </ul>
//      ))}
//    </div>
//  );
// };
const BookList = ({ books, removeBook }) => {		//add
  return (									//add
    <div>									//add
      {books.map(book => (						//add
        <ul key={book.id}>						//add
          <Book book={book} removeBook={removeBook} />	//add
        </ul>								//add
      ))}									//add
    </div>									//add
  );										//add
};										//add


// export default BookList;
export default connect(mapStateToProps,			//add 
mapDispatchToProps)(BookList);				//add

Finally, we have replaced almost all the code in BookList.js. On the top of the file we are importing ‘connect’ and ‘removeBook’. In this component we are both receiving data from Store as a book list to display on the screen and sending data to Store by way of ‘removeBook’ if the ‘remove’ button is clicked for a book.

Thus, we have defined both the methods ‘mapStateToProps’ and ‘mapDispatchToProps’ on top of the file and used ‘connect’ at the bottom of the file passing both these methods and connecting Store and BookList components.

Once your files are ready, run ‘npm start’ and you can see the working of your app exactly like before when you did not use Redux.

Conclusion

Redux is more powerful and is very useful in large applications. Here, we tried just to understand how Redux functions.  This knowledge should be useful when you start developing larger applications.

I am not a pro but a learner like you and I thought I should share the understanding I gained with the readers so that it would save some time for you.  I welcome suggestions from anyone to improve the content.

Happy coding :)

React app : https://github.com/IBTechRaj/redux-article-react

Redux app : https://github.com/IBTechRaj/redux-article-redux