Setting up Redux store in Your React application

rajandmr

Rajan Prasad

Posted on November 14, 2020

Setting up Redux store in Your React application

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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')
);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
// 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);
Enter fullscreen mode Exit fullscreen mode
// 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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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.

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
rajandmr
Rajan Prasad

Posted on November 14, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About