I am using React context to pass data through a particular journey in my app, and I have come to find that at the last step where I am rendering to the DOM and I want to perform a specific function within my useEffect
(it makes data layer push) that a sibling component is causing a re-render and the function is being triggered multiple times.
The sibling component in question triggers a function held within the context to update one of the context's values - Which will cause the context to re-render, which I believe all of my headaches are coming.
Is there a way in which I can get around this such that I do indeed trigger the context value update, but don't cause the component with the data layer push to be re-rendered?
See below for code breakdown - Full codesandbox here
My console logs are showing that things are rerendering twice as I would expect as my Wizard
component is updating the context value.
I understand that the ObjectUpdater
updating the context forces it to rerender, however I was under the impression that having memo
wrap the exported GaPushComponent
component would stop it from rerendering if nothing inside of it had changed? Or is it being forced to rerender because the parent is?
Extra research I've been looking into this more, and found this article that explicitly states:
If you are passing down an object on your React context provider and any property on it updates, what happens? Any component which consumes that context will re-render.
Code breakdown:
App.js
export default function App() {
useEffect(() => console.log("App.js"), []);
const [wizardOrNothing, setWizardOrNothing] = useState(null);
const toggleWizard = () =>
setWizardOrNothing((prev) => {
return prev === null ? <Wizard /> : null;
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={toggleWizard}>Start</button>
{wizardOrNothing}
</div>
);
}
MyContextProvider.js
const MyContextProvider = ({ children }) => {
const [myVal, setMyVal] = useState(null);
const [someObj, setSomeObj] = useState({});
const updateMyVal = (val) => setMyVal(myVal);
const updateSomeObj = (obj) => setSomeObj((prev) => ({ ...prev, ...obj }));
useEffect(() => console.log("MyContext.js"), []);
return (
<AppContext.Provider
value={{
myVal,
someObj,
updateMyVal,
updateSomeObj
}}
>
{children}
</AppContext.Provider>
);
};
export default memo(MyContextProvider);
Wizard.js
const Wizard = () => {
useEffect(() => console.log("Wizard.js"), []);
return (
<MyContextProvider>
<GaPushComponent />
<ObjectUpdater />
</MyContextProvider>
);
};
ObjectUpdater.js
const ObjectUpdater = () => {
const appContext = useContext(AppContext);
useEffect(() => {
console.log("ObjectUpdater.js");
appContext.updateSomeObj({ newData: "some new data" });
}, []);
return <div data-testid="object-updater" />;
};
export default memo(ObjectUpdater);
GaPushComponent.js
const GaPushComponent = () => {
useEffect(() => {
console.log("GaPushComponent.js");
console.warn("pushing data to GA...");
}, []);
return <div>My GA push component</div>;
};
export default memo(GaPushComponent);