javascriptreact-hooksvirtual-dom

Not rendering element after setState


I have a personal hook which is used to manage a list of counters:

import { useEffect, useState } from "react";
import { db } from "../services/database";

const useCounters = () => {
  const [counters, setCounters] = useState([]);
  console.log("Hook", counters);

  const addCounter = async (newCounter) => {
    try {
      await db.cards.add(newCounter);
      setCounters((prevState) => [...prevState, newCounter]);
    } catch (error) {
      console.error(error);
    }
  };

  const removeCounter = async (idToRemove) => {
    try {
      await db.cards.delete(idToRemove);
      setCounters(counters.filter((counter) => counter.id !== idToRemove));
    } catch (error) {
      console.error(error);
    }
  };

  const updateCounter = async (idToUpdate, newValue) => {
    try {
      await db.cards.update(idToUpdate, { value: newValue });
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    const loadData = async () => {
      db.cards.toArray().then((cards) => {
        setCounters(cards);
      });
    };
    loadData();
  }, []);

  return {
    counters,
    addCounter,
    removeCounter,
    updateCounter,
  };
};

export default useCounters;

And I have an element that is responsible for rendering these counters:

import Counter from "../components/Counter";
import AddCounter from "../components/AddCounter.js";
import Sleep from "../components/Sleep";

import useCounters from "../Hooks/useCounters";

import Style from "../css/pages/cards.module.css";

const Cards = () => {
  const { counters } = useCounters();
  console.log("Cards", counters);

  return (
    <div className={Style.cards}>
      {counters.map((item) => (
        <Counter
          key={item.id}
          id={item.id}
          title={item.title}
          initial={item.initialValue}
          value={item.value}
          color={item.color}
        />
      ))}
      <AddCounter />
      <Sleep />
    </div>
  );
};

export default Cards;

My problem is that when using the methods that manipulate the counters, it updates the state of the counters, but it does not render this in the cards component. Can someone explain to me what is happening and how to fix?

--

I tried to put 'counters' as a useEffect dependency but this generates an infinite rendering (eternal loop).

  useEffect(() => {
    const loadData = async () => {
      db.cards.toArray().then((cards) => {
        setCounters(cards);
      });
    };
    loadData();
  }, [counters]);

Solution

  • What happens here is that your counters state is not a global state, but a local state in every component that uses your hook.

    For example: If you use the hook in Cards and in AddCounter both components have a local version of that state. If you add a counter in AddCounter the counters variable there will change, but not in the Cards component. Have a look at this sandbox to see this behaviour.

    If you want to share your state between components, there are several ways:

    1. Lift the state up and pass it down via props
    2. Use the context api provided by react
    3. Use a state management solution like Redux or Zustand