Red Cloud
Red Cloud

Reputation: 129

How to set state within a reducer

I have an array of product objects inside my reducer and I have an empty array of brand as well. I want to add all the unique brands from my products object array into my brands array in my reducer, is there any way I can do that?

My Reducer:

import * as actionTypes from './shop-types';

 const INITIAL_STATE = {
 products: [
{
  id: 1,
  brand:"DNMX"
},
{
  id: 2,
  brand: "Aeropostale",
},
{
  id: 3,
  brand: "AJIO",
},
{
  id: 4,
  brand: "Nike",
},
],
  cart: [],
  brands: [], //<---- I want to add all the unique brands inside this array
  currentProduct: null,
};

  const shopReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
  case actionTypes.ADD_TO_CART:
  const product = state.products.find(
    (product) => product.id === action.payload.id
  );
  if (product !== null) {
    return {
      ...state,
      cart: state.cart.concat(product),
    };
  } else {
    return null;
  }
default:
  return state;
 }
 };

export default shopReducer;

Upvotes: 1

Views: 1752

Answers (2)

Brenden
Brenden

Reputation: 2097

It's not really clear if you mean unique brands by cart or products, but it shouldn't change the patterns you'll use to solve this.

First assuming the product list isn't changing, you can simply add them as part of the initial state.

const ALL_PRODUCTS = [
    { id: 1, brand:"DNMX" },
    { id: 2, brand: "Aeropostale" },
    { id: 3, brand: "AJIO" },
    { id: 4, brand: "Nike" }
];

const distinct = (array) => Array.from(new Set((array) => array.map((x) => x.brand)).entries());

const ALL_BRANDS = distinct(ALL_PRODUCTS.map((x) => x.brand));

const INITIAL_STATE = {
    products: ALL_PRODUCTS,
    cart: [],
    brands: ALL_BRANDS, 
    currentProduct: null,
};

If you will have an action that will add a new products and the brands have to reflect that you just apply the above logic during the state updates for the products.

const reducer = (state = INITIAL_STATE, action) => {
    switch(action.type) {
        case (action.type === "ADD_PRODUCT") {
            const products = [...state.products, action.product];

            return {
                ...state,
                products,
                brands: distinct(products.map((x) => x.brand))
            }
        }
    }

    return state;
};

Now for the idiomatic way. You might note that brands can be considered derived from the products collection. Meaning we probably dont even need it in state since we can use something called a selector to create derived values from our state which can greatly simplify our reducer/structure logic.

// we dont need brands in here anymore, we can derive.
const INITIAL_STATE = {
  products: ALL_PRODUCTS,
  cart: [],
  currentProduct: null;
};

const selectAllBrands = (state) => {
  return distinct(state.products.map((x) => x.brand))
};

Now when we add/remove/edit new products we no longer need to update the brand slice. It will be derived from the current state of products. On top of all of that, you can compose selectors just like you can with reducers to get some really complex logic without mucking up your store.

const selectCart = (state) => state.cart; 
const selectAllBrands = (state) => {...see above}  
const selectTopBrandInCart = (state) => {
  const cart = selectCart(state);
  const brands = selectBrands(brands);
  
  // find most popular brand in cart and return it.
};

I would highly recommend you check out reselect to help build composable and performant selectors.

Upvotes: 0

bopbopbop
bopbopbop

Reputation: 522

Brands is essentially derived data, meaning it's based off, or reliant on other data. Because of this, you don't actually need to set it in state, and instead rather, just derive it.

I'd normally recommend using Redux Toolkit as it's far simpler, but as you're using old-school Redux, I'd recommend using a library called Reselect. It's a library for creating memoized selectors that you can consume in your component.

For your example, I'd try something like:

// import the createSelector function 
// we'll use to make a selector
import { createSelector } from "reselect"

// create a "getter". this is just a simplified 
// way of accessing state
const selectBrands = (state) => state.products

// create the selector. this particular selector 
// just looks at `products` in state (from your 
// getter), and filters out duplicate values 
// and returns a unique list
const uniqueBrands = createSelector(selectBrands, (items) =>
  items.filter(
    (item, idx, arr) =>
      arr.findIndex((brand) => brand.name === item.name) === idx
  )
)

Then in your component code, you can access this in mapStateToProps:

const mapStateToProps = (state) => ({
  uniqueBrands: uniqueBrands(state),
})

This is currently untested, but should be what you're looking for.

Upvotes: 1

Related Questions