0

I am trying to understand the useContext hook a little bit better. I am playing around with this codesandbox which adds items from the left side component to the right side.

Now, I would like to count the number of times they are added to the list (how many times their resp. add to cart button has been clicked on) and display that next to them.

Do I need to create a completely new context and state hook to have access to the resp. items' values all over?

I tried implementing a counter in the addToItemList(item) function without success. I also tried to implement a counter function outside of it and then implement a global handleClick function in vain as well.

Thanks in advance for any tips and hints!

The code for the list of items:

import { useContext } from "react";
import { data, Item } from "./data";
import { ItemListContext } from "./ItemList.context";

const items: Item[] = data;
export default function ItemList() {
  const { itemList, setItemList } = useContext(ItemListContext); // get and set list for context

  const addItemToItemList = (item: Item) => {
    //you are using the itemList to see if item is already in the itemList
    if (!itemList.includes(item)) setItemList((prev) => [...prev, item]);
  };
  return (
    <div className="itemlist">
      {items.map((item, index) => (
        <div style={{ marginBottom: 15 }} key={index}>
          <div style={{ fontWeight: 800 }}>{item.name}</div>
          <div>{item.description}</div>
          <button onClick={() => addItemToItemList(item)}>
            Add to sidebar
          </button>
        </div>
      ))}
    </div>
  );
}


And the code for the container which contains the added items:

import { useContext } from "react";
import { ItemListContext } from "./ItemList.context";
import { Link, useHistory } from "react-router-dom";
export default function ItemContainer() {
  const history = useHistory(); //useHistory hooks doc: https://reactrouter.com/web/api/Hooks
  const { itemList } = useContext(ItemListContext);
  const onNavigate = () => {
    history.push("/selectedItemList");
  };
  return (
    <div style={{ flexGrow: 4 }}>
      <h1 style={{ textAlign: "center" }}>List of items</h1>

      <p>Number of items: {itemList.length}</p>
      {itemList.length > 0 && (
        <ul>
          {itemList.map((item, i) => (
            <li key={i}>{item.name}</li>
          ))}
        </ul>
      )}
      <div>
        <button>
          {" "}
          <Link to="/selectedItemList"> selected list details</Link>
        </button>
        <div>
          <button type="button" onClick={onNavigate}>
            selected list with useHistory hook
          </button>
        </div>
      </div>
    </div>
  );
}


ItemList.context.tsx

import React, {
  createContext,
  Dispatch,
  FunctionComponent,
  useState
} from "react";
import { Item } from "./data";
type ItemListContextType = {
  itemList: Item[]; // type of your items thata I declare in data.ts
  setItemList: Dispatch<React.SetStateAction<Item[]>>; //React setState type
};
export const ItemListContext = createContext<ItemListContextType>(
  {} as ItemListContextType
);

export const ItemListContextProvider: FunctionComponent = ({ children }) => {
  const [itemList, setItemList] = useState<Item[]>([]);

  return (
    <ItemListContext.Provider
      value={{ itemList: itemList, setItemList: setItemList }}
    >
      {children}
    </ItemListContext.Provider>
  );
};

NiHaoLiHai
  • 23
  • 1
  • 8

1 Answers1

1

Add wrapper methods to the context.

This assumes names are unique.

type ItemListContextType = {
  itemList: Item[]; // type of your items thata I declare in data.ts
  addItem: (item: Item) => void;
  removeItem: (item: Item) => void;
  counter: { [itemName: string]: number };
};
export const ItemListContext = createContext<ItemListContextType>(
  {} as ItemListContextType
);

export const ItemListContextProvider: FunctionComponent = ({ children }) => {
  const [itemList, setItemList] = useState<Item[]>([]);
  const [counter, setCounter] = useState<{ [itemName: string]: number }>({});

  const addItem = useCallback((item) => {
    setCounter((prev) => ({
      ...prev,
      [item.name]: (prev[item.name] || 0) + 1,
    }));
    setItemList((prev) => [...prev, item]);
  }, []);
  const removeItem = useCallback((itemName) =>
    setItemList((prev) => prev.filter((it) => it.name !== itemName)), []
  );

  return (
    <ItemListContext.Provider
      value={{ itemList: itemList, addItem, removeItem, counter }}
    >
      {children}
    </ItemListContext.Provider>
  );
};
John Smith
  • 3,863
  • 3
  • 24
  • 42
  • Hi again, thank you very much for your detailed answer. I implemented your code and refactored the other components so now I managed to make your new implementations of addItem and removeItem work. The only thing I'm struggling to access now is the counter itself. To use your new addItem, I just had to do `if (!itemList.includes(item)) addItem(item)` , what about the counter? Thank you again. I learnt a lot about useContext thanks to your answer! – NiHaoLiHai Apr 09 '21 at 21:56