Home > other >  My updated React context is not re-rendering the component
My updated React context is not re-rendering the component

Time:12-27

As an example, I have the below code:

Child component (where I want to access the latest activeGenerators list)

function MainGameHandler() {
  const resourceCtx = useContext(ResourceContext);

  const [show, setShow] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => {
      if (resourceCtx.activeGenerators.length > 0) {
        console.log("more than zero");
      } else {
        console.log("hm");
      }
    }, 1000);
    return () => clearInterval(interval);
  }, []);

 

Component that handles the context:

const defaultGeneratorState = {
  activeGenerators: [],
};

function generatorReducer(state, action) {
  if (action.type === "ADD") {
    const updatedGenerators = state.activeGenerators.concat(action.generator);

    return {
      activeGenerators: updatedGenerators,
    };
  }
}

function ResourceProvider(props) {
  const [atomicVal, setAtomicVal] = useState(0);
  const [energyVal, setEnergyVal] = useState(0);

  const [generatorState, dispatchGeneratorAction] = useReducer(
    generatorReducer,
    defaultGeneratorState
  );

  function addActiveGenerator(generator) {
    dispatchGeneratorAction({ type: "ADD", generator: generator });
  }

  function updateAtomicVal(value) {
    setAtomicVal((prevState) => prevState   value);
  }

  function updateEnergyVal(value) {
    setEnergyVal((prevState) => prevState   value);
  }

  const resourceContext = {
    atomicVal: atomicVal,
    energyVal: energyVal,
    updateAtomicVal: updateAtomicVal,
    updateEnergyVal: updateEnergyVal,
    addGenerator: addActiveGenerator,
    activeGenerators: generatorState.activeGenerators,
  };
  return (
    <ResourceContext.Provider value={resourceContext}>
      {props.children}
    </ResourceContext.Provider>
  );
}

export default ResourceProvider;

In another child component, I am updating the value "activeGenerators" in my context. However, this isn't triggering a re-render of the child component "MainGameHandler". If I manually make a minor change (like to the log string) and force the component to re-render, then I can see the code works as expected. Why isn't my context update re-rendering the component on its own, my understanding as that it should?

I'm checking if it's working by viewing the log from the useEffect() call in the child component.

Edit: The component that adds new items to activeGenerators

import { ListGroup } from "react-bootstrap";
import React from "react";
import { useContext } from "react";
import ResourceContext from "../store/resource-context";

function EnergyGeneratorsList() {
  const resourceCtx = useContext(ResourceContext);
  const generators = [
    {
      id: 1,
      name: "generator 1",
      production: 1,
      cost: 1,
    },
    {
      id: 2,
      name: "generator 2",
      production: 2,
      cost: 2,
    },
  ];

  const generatorList = generators.map((gen) => (
    <ListGroup.Item
      variant="dark"
      action
      key={gen.id}
      onClick={onGeneratorClick}
    >
      {gen.name}
    </ListGroup.Item>
  ));

  function onGeneratorClick(event) {
    const selectedGenerator = generators.filter((obj) => {
      return obj.name === event.target.innerText;
    });

    resourceCtx.addGenerator(selectedGenerator);
  }

  return (
    <React.Fragment>
      <ListGroup>{generatorList}</ListGroup>
    </React.Fragment>
  );
}

export default EnergyGeneratorsList;

CodePudding user response:

The useEffect function is executed with resourceCtx value from first render and won't receive any updates. This particular thing can be solved by passing it to a ref:

  const resourceCtx = useContext(ResourceContext);
  const resourceCtxRef = useRef();
  resourceCtxRef.current = resourceCtx;

  const [show, setShow] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => {
      // this should work as the ref is a mutable object that  receives updates
      if (resourceCtxRef.current.activeGenerators.length > 0) {
        console.log("more than zero");
      } else {
        console.log("hm");
      }
    }, 1000);
    return () => clearInterval(interval);
  }, []);

but it depends what exactly are you trying to do. I doubt that console.logging a value every second is what you will do at the end, so another solution may be more suitable to your particular case.

  • Related