Skip to content

Instantly share code, notes, and snippets.

@jeromeabel
Last active February 24, 2023 10:26
Show Gist options
  • Select an option

  • Save jeromeabel/0c558273e5a59b6cc61dcfcab1c75099 to your computer and use it in GitHub Desktop.

Select an option

Save jeromeabel/0c558273e5a59b6cc61dcfcab1c75099 to your computer and use it in GitHub Desktop.

Redux Toolkit Primer #2 - Cart 🛒

Redux Toolkit

A simple example of a Cart to remind me the basic usage of Redux Toolkit. We need to use two features : "products" and "cart". The Products React Component displays a list of products and add it to the cart. The Cart Component displays the number of items and the total score.

File structure :

  • ./main.jsx
  • ./App.jsx
  • ./app/store.js
  • ./features/products/Products.jsx
  • ./features/products/productsSlice.jsx
  • ./features/cart/Cart.jsx
  • ./features/cart/cartSlice.jsx

Thanks to : Modern Redux with Redux Toolkit (RTK) and TypeScript on Egghead.io

Visualization

rtk-primer-v2-cart-visu

main.jsx

As usual, encapsulate the App with the Provider.

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './app/store';

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

App.jsx

Useless App file just to import components.

import Products from './features/products/Products';
import Cart from './features/cart/Cart';
function App() { return <> <Products /><Counter /></> }
export default App;

store.js

Combine all reducers in a single file.

import { configureStore } from '@reduxjs/toolkit';
import productsReducer from '../features/products/productsSlice';
import cartReducer from '../features/cart/cartSlice';

export const store = configureStore({
  reducer: {
    cart: cartReducer,
    products: productsReducer,
  },
});

productsSlice.js

import { createSlice } from '@reduxjs/toolkit';

const productsSlice = createSlice({
  name: 'products',
  initialState: { products: {} },
  reducers: {
    loadProducts(state, action) {
      const products = action.payload;
      products.forEach((product) => {
        state.products[product.id] = product;
      });
    },
  },
});

export const { loadProducts } = productsSlice.actions;
export default productsSlice.reducer;

cartSlice.js

import { createSlice, createSelector } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: {} },
  reducers: {
    addToCart(state, action) {
      const id = action.payload;
      if (state.items[id]) state.items[id]++;
      else state.items[id] = 1;
    },
  },
});

export const { addToCart } = cartSlice.actions;
export default cartSlice.reducer;

//Selectors
export const getNumItems = (state) => {
  let num = 0;
  for (let id in state.cart.items) {
    num += state.cart.items[id];
  }
  return num;
};

export const getTotalPrice = createSelector(
  (state) => state.products.products,
  (state) => state.cart.items,
  (products, items) => {
    let total = 0;
    for (let id in items) {
      total += products[id].price * items[id];
    }
    return total.toFixed(2);
  }
);

Products.jsx

import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadProducts } from './productsSlice';
import { addToCart } from '../cart/cartSlice';

const DATA = [
  { name: 'Lamp', price: 10, id: 1 },
  { name: 'Usb Key', price: 15, id: 2 },
  { name: 'Apple', price: 1, id: 3 },
  { name: 'Book', price: 12, id: 4 },
  { name: 'Rice', price: 2.5, id: 5 },
];

const Products = () => {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(loadProducts(DATA));
  }, []);

  const products = useSelector((state) => state.products.products);

  return (
    <section>
      <h2>Products</h2>
      {Object.values(products).map((item) => (
        <div key={item.id}>
          {item.name} : {item.price}<button onClick={() => dispatch(addToCart(item.id))}>Add</button>
        </div>
      ))}
    </section>
  );
};

export default Products;

Cart.jsx

import { useSelector, useDispatch } from 'react-redux';
import { getNumItems, getTotalPrice } from './cartSlice';

const Cart = () => {
  const products = useSelector((state) => state.products.products);
  const items = useSelector((state) => state.cart.items);
  const nbItems = useSelector(getNumItems);
  const totalPrice = useSelector(getTotalPrice);

  return (
    <div>
      <h2>Cart : {nbItems}</h2>
      <hr />
      <ul>
        {Object.entries(items).map(([id, quantity]) => (
          <li key={products[id].id}>
            {products[id].name}- {quantity}
          </li>
        ))}
      </ul>
      <p>TOTAL : {totalPrice}</p>
    </div>
  );
};

export default Cart;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment