Setting up Redux store in Your React application

Subscribe to my newsletter and never miss my upcoming articles

State management is one of the most important portion of any Front end development framework. Almost every FE framework offers one or many state management libraries. For example, Redux & Recoil for React, Vuex for VueJS and NgRx for Angular. In this article, we're going to create a very simple Reading List application which will have a redux store set up and we will be using FakerAPI for the mock response.

You can checkout the demo application Here . Also, the source-code can be found Here on my GitHub. It is very basic application which fetches books from FakerAPI and you'll also be able to add Books.

Prerequisite

I assume that you already have a good understanding of React Components, Props & states(in general).

Getting Started

So we start simple by creating a React application using CRA and installing required dependencies afterwards.

create-react-app reading-list

This will generate the react application. Now, navigate into the newly created application and install the dependencies using

cd reading-list
npm install redux react-redux redux-thunk redux-devtools-extension axios

Now, few things to note here, redux alone is independent of any frameworks. react-redux is what let us use redux for react application. Also, we need some kind of middleware, redux-thunk in our case for basic Redux side effects logic, including complex synchronous logic that needs access to the store, and simple async logic like AJAX requests since With a plain basic Redux store, you can only do simple synchronous updates by dispatching an action. Middleware extends the store's abilities, and lets you write async logic that interacts with the store.

Also, redux-devtools-extension makes it easy to integrate Redux DevTools which speeds up our app debugging process. Axios works great for fetching data from the apis.

Folder Structure

Now, let's take a look at our folder structure

folder-structure.JPG

We'll create 3 folders actions , components & reducers inside our src folder. Inside the components folder, we'll create 3 components, BookList for looping through the List of Books, BookForm for adding a new Book & BookDetail for displaying each book's Detail.

Inside reducers folder, we'll have 2 files, index.js which will be our rootReducer & bookReducer.

Setting up the store

In order to set up the store, replace the src/index.js file with

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

// Imports for Redux
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';

// root reducer import
import rootReducer from './reducers';

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

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

Creating components

Since the store has been set up, we can start writing our components. Add the following code to build up our components:

// src/components/BookDetail.js
import React from 'react';

const BookDetails = ({ book }) => {
  return (
    <li>
      <div className="title">{book.title}</div>
      <div className="author">{book.author}</div>
    </li>
  );
};

export default BookDetails;
// src/components/BookForm.js
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { addBook } from '../actions/bookActions';

const BookForm = ({ dispatch }) => {
  const [title, setTitle] = useState('');
  const [author, setAuthor] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    const newBook = {
      title,
      author,
      id: 5,
    };
    dispatch(addBook(newBook));
    setTitle('');
    setAuthor('');
  };
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="book title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        required
      />
      <input
        type="text"
        placeholder="author"
        value={author}
        onChange={(e) => setAuthor(e.target.value)}
        required
      />

      <input type="submit" value="add book" />
    </form>
  );
};

export default connect(null)(BookForm);
// src/components/BookList.js

import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import BookDetails from './BookDetails';

import { fetchBooks } from '../actions/bookActions';

const BookList = ({ dispatch, books }) => {
  useEffect(() => {
    dispatch(fetchBooks());
  }, [dispatch]);
  return books.length ? (
    <div className="book-list">
      <ul>
        {books.map((book) => {
          return <BookDetails book={book} key={book.id} />;
        })}
      </ul>
    </div>
  ) : (
    <div className="empty">No books to read</div>
  );
};

const mapStateToProps = (state) => ({
  books: state.books.books,
});

export default connect(mapStateToProps)(BookList);

Setting up Reducers

Reducers are what responsible for mutating states. It reads the dispatched action type and mutates the state accordingly. There is one main reducer generally called rootReducer which keeps track of all the other reducers. If you look at src/index.js, in createStore method, we only passed on Reducer which is root Reducer. The rootReducer contains all the other reducers.

Add the following code in src/reducers/index.js

import { combineReducers } from 'redux';

import booksReducer from './booksReducer';

const rootReducer = combineReducers({
  books: booksReducer,
});

export default rootReducer;

We see that it is calling combineReducers method from redux and taking all the other reducers.

Add the following code to src/reducers/bookReducer.js

import { GET_BOOKS, ADD_BOOK } from '../actions/bookActions';

export const initialState = {
  books: [],
};

export default function bookReducer(state = initialState, action) {
  switch (action.type) {
    case GET_BOOKS:
      return {
        ...state,
        books: action.payload,
      };

    case ADD_BOOK:
      return {
        ...state,
        books: [...state.books, action.payload],
      };

    default:
      return state;
  }
}

Setting up actions

Add the following code to src/actions/bookActions.js

import Axios from 'axios';
export const GET_BOOKS = 'GET_BOOKS';
export const ADD_BOOK = 'ADD_BOOk';

export const fetchBooks = () => async (dispatch) => {
  const data = await fetchData();
  dispatch({
    type: GET_BOOKS,
    payload: data,
  });
};

export const addBook = (newBook) => async (dispatch) => {
  dispatch({
    type: ADD_BOOK,
    payload: newBook,
  });
};

// fetch data from the API
const fetchData = async () => {
  try {
    const res = await Axios.get(
      'https://fakerapi.it/api/v1/custom?_quantity=5&author=name&id=counter&title=city'
    );

    return res.data.data;
  } catch (error) {
    console.log(error);
  }
};

Styling

Since we're mainly focused on setting up redux, it doesn't mean our application has to look ugly. That's why i've already written some basic styling which will make our app look decent.

Replace all the codes in src/index.css with following

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background: #553055;
}
.App {
  background: #4c2a4c;
  margin: 20px auto;
  width: 90%;
  max-width: 700px;
  color: #eee;
}
.navbar {
  padding: 10px 20px;
  text-align: center;
  background: #6d3d6d;
}
.navbar h1 {
  margin: 10px 0;
}

.book-list {
  margin: 20px;
}
.book-list ul {
  padding: 0;
  list-style-type: none;
}
.book-list li {
  background: #6d3d6d;
  border-radius: 4px;
  padding: 10px;
  cursor: pointer;
  margin: 10px 0;
}
.book-list li:hover {
  opacity: 0.7;
  text-decoration: line-through;
}
.book-list .title {
  font-weight: bold;
  color: #fff;
  font-size: 1.2em;
}
.book-list .author {
  font-size: 0.9em;
  color: #ddd;
}
.empty {
  margin: 20px;
  text-align: center;
}

form {
  padding: 20px;
}
input[type='text'] {
  width: 100%;
  padding: 10px;
  box-sizing: border-box;
  margin: 6px 0;
  background: #3c1f3c;
  color: #fff;
  border: 0;
}
input[type='submit'] {
  margin: 10px auto;
  background: #eee;
  border: 0;
  padding: 6px 20px;
  display: block;
}

Now, finally, let's add our components to src/App.js . Replace all the codes in src/App.js with following

import BookForm from './components/BookForm';
import BookList from './components/BookList';

function App() {
  return (
    <div className="App">
      <BookList />
      <BookForm />
    </div>
  );
}

export default App;

Now, if you've followed everything accordingly, once you start the server, you can see the application running. Also if you look at Redux DevTools you'll be able to see how the states changed, what actions were launched.

If you run in any problem, you can always use the code from Here as a reference.

No Comments Yet