Search code examples
reactjsreduxreact-hooksimmer.js

How to remove elements from array using redux, immer, react


import produce from "immer";

const initialState = {
  isLoading: true,
  error: "",
  burgers: [],
};

export default function (state = initialState, action) {
  switch (action.type) {
    case "ADD_BURGER_BUCKET": {
      return produce(state, (draftState) => {
        if (Array.isArray(action.payload)) {
          draftState.burgers.push(...action.payload);
        } else {
          draftState.burgers.push(action.payload);
        }
      });
    }
    case "REMOVE_BURGERS_BUCKET": {
      return produce(state, (draftState) => {
        draftState.burgers = []
      });
    }
    **case "REMOVE_ONE_BURGER_BUCKET": {
      return produce(state, (draftState) => {
        console.log(action.payload, draftState.burgers) console.log => 3 Proxy {0: {…}}
        draftState.burgers.filter(el => el.id !== action.payload)
      })
    }** HERE THIS ONE DOES NOT WORK!!!
    default:
      return state;
  }
}


return ( <===== BURGER BUTTON
                <Burger
                  key={burger.id}
                  text={burger.name}
                  cost={burger.cost}
                  onClick={() => {
                    dispatch({
                      type: "REMOVE_ONE_BURGER_BUCKET",
                      payload: burger.id, <=== PASS ID TO REDUCER
                    }); <==== THIS ONE DOESN'T REMOVE THE ELEMENT FROM AN ARRAY
                    localStorage.setItem("burger", JSON.stringify(burger));
                    localStorage.setItem(
                      "burgersBucket",
                      JSON.stringify(
                        list.burgers.filter((el) => el.id !== burger.id)
                      )
                    );
                    history.push("/redo");
                  }}
                />
              );
            }

I want to remove element from array by its id, but I can't do it, that's what I get in the console

3 Proxy {0: {…}}

I have useselector and useDispatch hooks both imported.

FULL CODE

    import React from "react";

import { Wrapper } from "../../styled/general";
import { Menu, MenuTitle, Burgers } from "../home/homestyled";
import Burger from "../../component/burger";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { useHistory } from "react-router-dom";

export default function Index() {
  const list = useSelector((state) => state.burgerBucket);
  const dispatch = useDispatch();
  const history = useHistory();

  useEffect(() => {
    console.log(list.burgers);
    if (list.burgers.length > 0) {
      localStorage.setItem("burgersBucket", JSON.stringify(list.burgers));
    } else {
      let burgersBucket = JSON.parse(localStorage.getItem("burgersBucket"));
      dispatch({ type: "ADD_BURGER_BUCKET", payload: burgersBucket });
    }
  }, []);

  return (
    <Wrapper>
      <Menu>
        <MenuTitle>My Bucket</MenuTitle>
        <Burgers>
          {[...list.burgers, { finish: true }, { addMore: true }].map(
            (burger) => {
              if (burger.addMore) {
                return (
                  <Burger
                    key={-2}
                    bg={"lightgreen"}
                    text={"Додати ще"}
                    onClick={() => {
                      history.push("/");
                    }}
                  />
                );
              }
              if (burger.finish) {
                return (
                  <Burger
                    key={-1}
                    bg={"#ff5050"}
                    text={"Завершити"}
                    onClick={() => {
                      dispatch({ type: "REMOVE_BURGERS_BUCKET" });
                      history.push("/");
                    }}
                  />
                );
              }
              return (
                <Burger
                  key={burger.id}
                  text={burger.name}
                  cost={burger.cost}
                  onClick={() => {
                    dispatch({
                      type: "REMOVE_ONE_BURGER_BUCKET",
                      payload: burger.id,
                    });
                    localStorage.setItem("burger", JSON.stringify(burger));
                    localStorage.setItem(
                      "burgersBucket",
                      JSON.stringify(
                        list.burgers.filter((el) => el.id !== burger.id)
                      )
                    );
                    history.push("/redo");
                  }}
                />
              );
            }
          )}
        </Burgers>
      </Menu>
    </Wrapper>
  );
}
enter code here

FULL CODE REDUCER

import produce from "immer";

const initialState = {
  isLoading: true,
  error: "",
  burgers: [],
};

export default function (state = initialState, action) {
  switch (action.type) {
    case "ADD_BURGER_BUCKET": {
      return produce(state, (draftState) => {
        if (Array.isArray(action.payload)) {
          draftState.burgers.push(...action.payload);
        } else {
          draftState.burgers.push(action.payload);
        }
      });
    }
    case "REMOVE_BURGERS_BUCKET": {
      return produce(state, (draftState) => {
        draftState.burgers = []
      });
    }
    case "REMOVE_ONE_BURGER_BUCKET": {
      return produce(state, (draftState) => {
        console.log(action.payload, draftState.burgers)
        draftState.burgers.filter(el => el.id !== action.payload)
      })
    }
    default:
      return state;
  }
}

ALSO MAYBE THIS ONE WILL BE IMPORTANT, THIS IS THE CODE(PAGE) I GET REDIRECTED ONCE THE USER CLICKED THAT BURGER-BUTTON

FULL CODE

import React, { useEffect, useState, useContext } from "react";

import { Wrapper } from "../../styled/general";
import { Menu, MenuTitle } from "../home/homestyled";
import {
  BurgerIngridients,
  IngridientWrapper,
  BurgerDetails,
  DetailsTitle,
  IngridientsDetails,
  Total,
  DetailsButtonContinue,
} from "./burgerredostyled";
import Ingridient from "../../component/Ingridient";
import IngridientdetailBlock from "../../component/IngridientsDetailBlock";

import { useHistory } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { useBurger } from "../../hooks/useBurger";

import { sumOfToppings } from "../../helpers/sum";

export default function Index() {
  const burger = useSelector((state) => state.burger);
  const [toppings, error, isLoading] = useBurger(
    "http://localhost:3000/toppings"
  );
  const dispatch = useDispatch();
  const history = useHistory();

  useEffect(() => {
    if (burger.id) {
      localStorage.setItem("burger", JSON.stringify(burger));
    } else {
      let localStorageBurger = JSON.parse(localStorage.getItem("burger"));
      dispatch({ type: "ADD_BURGER", payload: localStorageBurger });
    }

    for (let i = 0; i < toppings.length; i++) {
      for (let j = 0; j < burger.consists_of.length; j++) {
        if (burger.consists_of[j].name === toppings[i].name) {
          toppings[i].quantity = burger.consists_of[j].quantity;
        }
      }
    }

    if (toppings.length > 0) {
      dispatch({
        type: "ADD_BURGER",
        payload: { ...burger, consists_of: toppings },
      });
    }
  }, [isLoading]);

  const add = (text, num) => {
    for (let i = 0; i < toppings.length; i++) {
      if (toppings[i].name === text) {
        toppings[i].quantity = num;
      }
    }
    dispatch({
      type: "ADD_BURGER",
      payload: { ...burger, consists_of: toppings },
    });
    localStorage.setItem(
      "burger",
      JSON.stringify({ ...burger, consists_of: toppings })
    );
  };

  if (!isLoading)
    return (
      <Wrapper>
        <Menu>
          <MenuTitle>{burger && burger.name}</MenuTitle>
          <IngridientWrapper>
            <BurgerIngridients>
              {burger.consists_of.map(({ name, quantity, id }) => {
                return (
                  <Ingridient
                    key={id}
                    text={name}
                    add={add}
                    initValue={quantity}
                  />
                );
              })}
            </BurgerIngridients>
            <BurgerDetails>
              <DetailsTitle>
                <span>{burger && burger.name} | інформація</span>{" "}
                <DetailsButtonContinue
                  onClick={() => {
                    dispatch({
                      type: "ADD_BURGER_BUCKET",
                      payload: { ...burger, cost: sumOfToppings(burger) },
                    });
                    history.push("/bucket");
                  }}
                >
                  продовжити
                </DetailsButtonContinue>
              </DetailsTitle>
              <IngridientsDetails>
                {burger.consists_of
                  .filter((el) => el.quantity > 0)
                  .map((el) => {
                    return (
                      <IngridientdetailBlock
                        key={el.id}
                        text={el.name}
                        price={el.quantity * el.cost}
                        qty={el.quantity}
                      ></IngridientdetailBlock>
                    );
                  })}
              </IngridientsDetails>
              <Total>Загалом {sumOfToppings(burger)}(грн.)</Total>
            </BurgerDetails>
          </IngridientWrapper>
        </Menu>
      </Wrapper>
    );

  if (isLoading) {
    return <span>loading</span>;
  }
}

Solution

  • Array.prototype.filter does not mutate the array, it creates a new one.

    So this:

    draftState.burgers.filter(el => el.id !== action.payload)

    is not actually changing draftState.burgers. But this will:

    produce(state, (draftState) => {
      draftState.burgers = draftState.burgers.filter(el => el.id !== action.payload)
    })