skip to Main Content

I am a beginner and i have a structure like this:

<CountContext.Provider value={{ contextCount, setContextCount }}>
        <Wrapper />
        <Bar />
 </CountContext.Provider>

Inside the wrapper I have 12 cards, each with a click counter.
The bar contains a counter of all clicks, implemented through the context, as well as a reset button.
How can I, without using third-party libraries, reset not only the general counter, but also reset each individual counter on the cards?

More details:

export const CountContext = createContext();
const App = () => {
  const [contextCount, setContextCount] = useState(0);
 
  return (
    <div className="app">
      <CountContext.Provider value={{ contextCount, setContextCount }}>
        <Wrapper />
        <Bar />
      </CountContext.Provider>
    </div>
  );
};

export default App;
const Wrapper = () => {

  return (
    <div className="wrapper">
      {cards.map((card) => (
        <Card color={card.color} key={card.id} />
      ))}
    </div>
  );
};

export default Wrapper;
const Card = ({ color }) => {
  const { contextCount, setContextCount } = useContext(CountContext);
  const [count, setCount] = useState(0);

  const clickOnCard = () => {
    setCount(count + 1);
    setContextCount((prev) => prev + 1);
  };
 
  return (
    <div
      className="card"
      style={{ backgroundColor: color }}
      onClick={clickOnCard}
    >
      {count}
    </div>
  );
};

export default Card;
const Bar = () => {
  const { contextCount, setContextCount, styleBtn, setStyleBtn } =
    useContext(CountContext);
  const reset = () => {
    setContextCount(0);
  };

  return (
    <div className="bar">
      Total Count:{contextCount}
      <button className="btn" onClick={reset}>
        Reset
      </button>
    </div>
  );
};

I try useEffect, context, if-else, conditional render and some other ways.

2

Answers


  1. To achieve this without using third-party libraries, you can follow these steps:

    1. Modify Context:
      Update your CountContext to include a function for resetting individual card counters. This function can be passed down to each card.

      const CountContext = createContext();
      
      const CountProvider = ({ children }) => {
        const [contextCount, setContextCount] = useState(0);
      
        const resetAllCounters = () => {
          setContextCount(0);
        };
      
        return (
          <CountContext.Provider value={{ contextCount, setContextCount, resetAllCounters }}>
            {children}
          </CountContext.Provider>
        );
      };
      
    2. Update Cards:
      Pass the reset function down to each card and use it to reset the individual counters.

      const Card = ({ resetCardCounter }) => {
        const [cardCount, setCardCount] = useState(0);
      
        const handleCardClick = () => {
          setCardCount(cardCount + 1);
          resetCardCounter(); // Reset individual card counter
        };
      
        return (
          <div>
            <p>Card Counter: {cardCount}</p>
            <button onClick={handleCardClick}>Click me</button>
          </div>
        );
      };
      
    3. Update Wrapper:
      In the Wrapper component, render multiple instances of the Card component and pass the reset function to each card.

      const Wrapper = () => {
        const { resetAllCounters } = useContext(CountContext);
      
        const resetCardCounter = () => {
          // Logic to reset individual card counter
        };
      
        return (
          <div>
            {Array.from({ length: 12 }, (_, index) => (
              <Card key={index} resetCardCounter={resetCardCounter} />
            ))}
          </div>
        );
      };
      
    4. Update Bar:
      In the Bar component, use the context to access the resetAllCounters function and implement the reset button.

      const Bar = () => {
        const { contextCount, resetAllCounters } = useContext(CountContext);
      
        return (
          <div>
            <p>Total Count: {contextCount}</p>
            <button onClick={resetAllCounters}>Reset All Counters</button>
          </div>
        );
      };
      

    Now, when you click the "Reset All Counters" button in the Bar component, it will reset both the general counter and the individual counters on each card. Adjust the logic inside the resetCardCounter function in the Wrapper component based on your requirements for resetting individual card counters.

    Login or Signup to reply.
  2. This is a classic "state hoisting" problem. The state of the counters for each card is currently kept in the Card component itself, so they are not within the scope of the context to be able to reset.

    Option A – State Hoisting

    The answer in these situations is usually to move that state upwards to bring it into scope.

    To do this, we can store each number for each card inside the context provider in the form of a map where the key is the card ID and the value is the current count for that card. We will then pass accessors down so the cards can use the count and also provide a method to reset them.

    export const CountContext = createContext();
    const App = () => {
      const [counts, setCounts] = useState({});
    
      const getCount = useCallback((id) => counts[id] ?? 0, [counts]);
    
      const incrementCount = useCallback(
        (id) => {
          setCounts((prevCounts) => ({
            ...prevCounts,
            [id]: (prevCounts[id] ?? 0) + 1,
          }));
        },
        [counts],
      );
    
      const resetAllCounters = useCallback(() => {
        setCounts({});
      }, []);
    
      const totalCount = useMemo(
        () =>
          Object.values(counts).reduce(
            (accumulator, cardCount) => accumulator + cardCount,
            0,
          ),
        [counts],
      );
    
      return (
        <div className="app">
          <CountContext.Provider value={{ incrementCount, totalCount, resetAllCounters, getCount }}>
            <Wrapper />
            <Bar />
          </CountContext.Provider>
        </div>
      );
    };
    
    export default App;
    
    

    Wrapper is almost the same apart from we pass down the ID as a normal prop so the card can use it to identify the right count.

    const Wrapper = () => {
    
      return (
        <div className="wrapper">
          {cards.map((card) => (
            <Card color={card.color} id={card.id} key={card.id} />
          ))}
        </div>
      );
    };
    
    export default Wrapper;
    

    Card now relies on the context for its current value and to update it.

    const Card = ({ color, id }) => {
      const { incrementCount, getCount } = useContext(CountContext);
    
      const clickOnCard = () => {
        incrementCount(id);
      };
     
      return (
        <div
          className="card"
          style={{ backgroundColor: color }}
          onClick={clickOnCard}
        >
          {getCount(id)}
        </div>
      );
    };
    
    export default Card;
    

    Bar just needs rewiring a little for the new naming:

    const Bar = () => {
      const { totalCount, resetAllCounters } = useContext(CountContext);
    
      return (
        <div>
          <p>Total Count: {totalCount}</p>
          <button onClick={resetAllCounters}>Reset All Counters</button>
        </div>
      );
    };
    

    If you need to deal with the total count reducing if one of the Card unmounts you could achieve it using effects.

    Option B – Forced Reset

    This is far simpler, but must be used with caution and not become the default way of doing this without considering the trade-offs each time.

    In this solution, you utilise React’s key property. When this changes on a given component, it causes that component and all of its children to completely unmount and remount, which has the effect of clearing their state also.

    The reason for being cautious is this means you are totally giving up on React performant diffing algorithm and just building up the DOM all over again when they hit reset. For trivial apps though, you might not care.

    Also, it will wipe out any other transient state you have in the children. It’s a "sledgehammer" approach. But it can be useful sometimes if you don’t care about the above impacts and it has a bonus in that you don’t have to manage the resets yourself.

    In App:

    export const CountContext = createContext();
    const App = () => {
      const [contextCount, setContextCount] = useState(0);
      const [resetKey, setResetKey] = useState(0)
    
      const resetIndividualCounters = useCallback(() => {
         // Each time reset is called, change the key to force the tree to be recreated.
         setResetKey(prev => prev + 1)
      }, [])
     
      return (
        <div className="app">
          <CountContext.Provider value={{ contextCount, setContextCount, resetIndividualCounters }}>
            <Wrapper key={resetKey} />
            <Bar />
          </CountContext.Provider>
        </div>
      );
    };
    
    export default App;
    

    In Bar

    const Bar = () => {
      const { resetIndividualCounters, contextCount, setContextCount, styleBtn, setStyleBtn } =
        useContext(CountContext);
      const reset = () => {
        setContextCount(0);
        resetIndividualCounters()
      };
    
      return (
        <div className="bar">
          Total Count:{contextCount}
          <button className="btn" onClick={reset}>
            Reset
          </button>
        </div>
      );
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search