Open In App

How to persist Redux state in local Storage without any external library?

Improve
Improve
Like Article
Like
Save
Share
Report

Redux as per the official documentation is a predictable state container for JavaScript apps. In simple words, it is a state management library, with the help of Redux managing the state of components becomes very easy. We can manage the state of the app by creating a global state known as a store.

The idea to use Redux may be fine for a complex react app but this state is not persistable throughout. It means that once you reload the browser the state of the app changes and reaches its default state. Persisting the data of such react apps is very easy. We will be using local storage to store the current state of the React app and persist the data even on reloads. 

Creating React Application And Installing Module:

Step 1: Create a React application using the following command:

npx create-react-app myapp

Step 2: After creating your project folder i.e. myapp, move to it using the following command:

cd myapp

Step 3: After creating the ReactJS application, Install the required modules using the following command:

npm install redux
npm install react-redux

Project Structure: It will look like the following.

Project Structure

Example: We will create a simple shopping cart application through which we will persist data to our local storage.

Filename- App.js This is the App component of our React app. All the dependencies that are required for a typical react application are imported. The provider function is imported from react-redux. This will act as a wrapping component for our App, to this wrapping component we will pass the store. The store is basically a global state of our application.

Javascript




import React from "react";
import { Provider } from 'react-redux';
import CartContainer from "./components/CartContainer";
 
// Store
import { store } from './store';
import { saveState } from './localStorage';
 
store.subscribe(() => {
  saveState({
    cart: store.getState().cart,
    total: store.getState().total,
    amount: store.getState().amount
  });
});
 
// Items
const cartItems = [
  {
    id: 1,
    title: "Samsung",
    price: 799.99,
    img:
      "shorturl.at/ajkq9",
    amount: 1
  },
  {
    id: 2,
    title: "Google pixel Max",
    price: 399.99,
    img:
      "shorturl.at/ajkq9",
    amount: 1
  },
  {
    id: 3,
    title: "Xiaomi",
    price: 999.99,
    img:
      "shorturl.at/ajkq9",
    amount: 1
  }
];
 
function App() {
  return (
    <Provider store={store}>
      <CartContainer cart={cartItems} />
    </Provider>
  );
}
 
export default App;


Filename- store.js In this file, the basic store set up is done using redux. The store is initialized and the state is being persisted to local storage. The redux store contains total, amount, and cart items. This store’s state will be later saved in the local storage.

Javascript




import reducer from './reducer';
import { createStore } from 'redux';
import { loadState } from './localStorage';
 
const cartItems = [
  {
    id: 1,
    title: "Samsung",
    price: 799.99,
    img:
      "shorturl.at/ajkq9",
    amount: 1
  },
  {
    id: 2,
    title: "Google pixel Max",
    price: 399.99,
    img:
      "shorturl.at/ajkq9",
    amount: 1
  },
  {
    id: 3,
    title: "Xiaomi",
    price: 999.99,
    img:
      "shorturl.at/ajkq9",
    amount: 1
  }
];
 
const persistedState = loadState();
 
const initialStore = {
  cart: cartItems,
  amount: 0,
  total: 0,
  persistedState
}
 
export const store = createStore(reducer, persistedState);


Filename: reducer.js This file contains the reducer function. As per the action dispatched through the UI, the corresponding functionality takes place. Mainly our reducer will be dealing with 5 basic operations of our app namely:

  • The DECREASE action decreases the quantity of the items in our cart.
  • The INCREASE action increases the quantity of the items in our cart.
  • The REMOVE action removes an item from the cart.
  • The CLEAR_CART action basically clears the entire cart.
  • The GET_TOTALS action gets the sum of all the items in our cart.

Note: One important thing to remember is that do not mutate the state of the app while using Redux. 

Javascript




import {
  INCREASE,
  DECREASE,
  REMOVE,
  CLEAR_CART,
  GET_TOTALS,
} from './actions';
 
function reducer(state, action) {
  if (action.type === DECREASE) {
    return {
      ...state, cart: state.cart.map((item) => {
        if (item.id === action.payload.id) {
          if (item.amount === 0) {
            return item;
          } else {
            item.amount--;
          }
        }
        return item;
      })
    }
  }
  if (action.type === INCREASE) {
    return {
      ...state, cart: state.cart.map((item) => {
        if (item.id === action.payload.id) {
          item.amount++;
        }
        return item;
      })
    }
  }
  if (action.type === CLEAR_CART) {
    return { ...state, cart: [] };
  }
  if (action.type === REMOVE) {
    return {...state, cart: state.cart.filter(item => item.id !== action.payload.id)}
  }
  if (action.type === GET_TOTALS) {
    let { total, amount } = state.cart.reduce((cartTotal, cartItem) => {
      const { price, amount } = cartItem;
      cartTotal.amount += amount;
      cartTotal.total += Math.floor(amount * price);
      return cartTotal;
    }, { amount: 0, total: 0 });
    return { ...state, total, amount };
  }
  return state;
}
 
export default reducer;


Filename: CartItem.js This file contains the code for the cartItem component. It is in this file that different methods are dispatched. The function mapDispatchToProps include three actions DECREASE, INCREASE, REMOVE.

Javascript




import React from "react";
import { connect } from 'react-redux';
import { DECREASE, INCREASE, REMOVE } from '../actions';
 
const CartItem = ({ title, price, img, amount, increase, decrease, remove }) => {
  return (
    <div className="cart-item">
      <img src={img} alt={img} />
      <div>
        <h4>{title}</h4>
        <h4 className="item-price">${price}</h4>
 
      </div>
      <div>
        {/* increase amount */}
        <button className="amount-btn"
          onClick={() => increase()}>
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
            <path
      d="M10.707 7.05L10 6.343 4.343 12l1.414 1.414L10 9.172l4.243 4.242L15.657 12z"
            />
          </svg>
        </button>
        {/* amount */}
        <p className="amount">{amount}</p>
 
 
        {/* decrease amount */}
        <button className="amount-btn"
          onClick={() => decrease()} >
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
            <path
      d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
            />
          </svg>
        </button>
      </div>
    </div>
  );
};
 
const mapDispatchToProps = (dispatch, ownProps) => {
  const { id, amount } = ownProps;
  return {
    increase: () => dispatch({ type: INCREASE, payload: { id } }),
    decrease: () => dispatch({ type: DECREASE, payload: { id } }),
    remove: () => dispatch({ type: REMOVE, payload: { id, amount } })
  }
}
 
export default connect(null, mapDispatchToProps)(CartItem);


Filename- CartContainer.js This file imports the CartItem component and passes them the props that are needed. Here we connect mapStateToProps and mapDispatchToProps and then pass CartContainer to it.

Javascript




import React from "react";
import CartItem from './CartItem';
import { connect } from 'react-redux';
import { CLEAR_CART, GET_TOTALS } from '../actions';
 
const CartContainer = ({ cart = [], total, remove, getTotal }) => {
  React.useEffect(() => {
    getTotal();
  })
  if (cart.length === 0) {
    return (
      <section className="cart">
 
        <header>
          <h2>your bag</h2>
          <h4 className="empty-cart">
              is currently empty
          </h4>
        </header>
      </section>
    );
  }
  return (
    <section className="cart">
      {/* cart header */}
      <header>
        <h2>your bag</h2>
      </header>
      {/* cart items */}
      <article>
        {cart.map((item) => {
          return <CartItem key={item.id} {...item} />
        })}
      </article>
      {/* cart footer */}
      <footer>
        <hr />
        <div className="cart-total">
          <h4>
            total <span>${total}</span>
          </h4>
        </div>
        <button className="btn clear-btn"
          onClick={() => remove()} >clear cart</button>
      </footer>
    </section>
  );
};
 
function mapStateToProps(store) {
  const { cart, total } = store;
  return { cart: cart, total: total };
}
 
function mapDispatchToProps(dispatch) {
  return {
    remove: () => dispatch({ type: CLEAR_CART }),
    getTotal: () => dispatch({ type: GET_TOTALS })
  }
}
 
export default connect(mapStateToProps, mapDispatchToProps)(CartContainer);


Filename- localStorage.js Now we will be adding the localStorage.js file. The method of persisting of data requires only four simple steps:

Step 1: Create a file named localStorage.js in the root folder typically in the src folder of your react app. In this file, we will be adding two methods one to load state from the local storage and the other method to save state to the local storage. The code for loadState method is as follows:

Javascript




export const loadState = () => {
    try {
      const serialState = localStorage.getItem('appState');
      if (serialState === null) {
        return undefined;
      }
      return JSON.parse(serialState);
    } catch (err) {
      return undefined;
    }
};


In the local storage, data is stored in the form of key-value pairs. Here the key is ‘appState’ and the value will be the actual state of the app.

Step 2: Code for saveState method is as follows:

Javascript




export const saveState = (state) => {
    try {
      const serialState = JSON.stringify(state);
      localStorage.setItem('appState', serialState);
    } catch(err) {
        console.log(err);
    }
};


Step 3: Now in the store.js file import the loadState method from the localStorage.js file and get its value in the persistedState constant. Now as an object put this persistedState constant with the actual state of your app and export it by passing it to the store.

Filename- store.js

Javascript




import reducer from './reducer';
import {createStore} from 'redux';
import {loadState} from './localStorage';
 
const persistedState = loadState();
 
const initialStore={
    /* state of your app */
    cart:cartItems,
    amount:0,
    total:0,
    persistedState
}
    
export const store=createStore(reducer,persistedState);


Step 4: This is the most important step as this involves saving the state to the local storage of the browser. Now in the App.js component import the store from the store.js file and saveState() method from the localStorage.js file. Now save the state of the app by calling the subscribe function from the store.

Filename- App.js

Javascript




import {store} from './store';
import {saveState} from './localStorage';
 
store.subscribe(() => {
  saveState({
   /* example state */
    cart:store.getState().cart,
    total:store.getState().total,
    amount: store.getState().amount
  });
});


Now just pass this store to the Provider component in the App component and the data will become persistent in the local storage. 

Output: You can check the state by opening developer tools on chrome and then navigating to Application then to Storage than to local storage. In the local storage, you’ll see the key named appState. This is the place where the entire data is stored.



Last Updated : 13 Jun, 2022
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads