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.