I've got a simple example of React Context that uses useMemo to memoize a function and all child components re-render when any are clicked. I've tried several alternatives (commented out) and none work. Please see code at stackblitz and below.
https://stackblitz.com/edit/react-yo4eth
Index.js
import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import { GlobalProvider } from "./GlobalState";
function App() {
return (
<GlobalProvider>
<Hello />
</GlobalProvider>
);
}
render(<App />, document.getElementById("root"));
GlobalState.js
import React, {
createContext,useState,useCallback,useMemo
} from "react";
export const GlobalContext = createContext({});
export const GlobalProvider = ({ children }) => {
const [speakerList, setSpeakerList] = useState([
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true },
]);
const clickFunction = useCallback((speakerIdClicked) => {
setSpeakerList((currentState) => {
return currentState.map((rec) => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
});
});
},[]);
// const provider = useMemo(() => {
// return { clickFunction: clickFunction, speakerList: speakerList };
// }, []);
//const provider = { clickFunction: clickFunction, speakerList: speakerList };
const provider = {
clickFunction: useMemo(() => clickFunction,[]),
speakerList: speakerList,
};
return (
<GlobalContext.Provider value={provider}>{children}</GlobalContext.Provider>
);
};
Hello.js
import React, {useContext} from "react";
import Speaker from "./Speaker";
import { GlobalContext } from './GlobalState';
export default () => {
const { speakerList } = useContext(GlobalContext);
return (
<div>
{speakerList.map((rec) => {
return <Speaker speaker={rec} key={rec.id}></Speaker>;
})}
</div>
);
};
Speaker.js
import React, { useContext } from "react";
import { GlobalContext } from "./GlobalState";
export default React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const { clickFunction } = useContext(GlobalContext);
return (
<>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</>
);
});
Couple of problems in your code:
You already have memoized the clickFunction
with useCallback
, no need to use useMemo
hook.
You are consuming the Context
in Speaker
component. That is what's causing the re-render of all the instances of Speaker
component.
Solution:
Since you don't want to pass clickFunction
as a prop from Hello
component to Speaker
component and want to access clickFunction
directly in Speaker
component, you can create a separate Context
for clickFunction
.
This will work because extracting clickFunction
in a separate Context
will allow Speaker
component to not consume GlobalContext
. When any button is clicked, GlobalContext
will be updated, leading to the re-render of all the components consuming the GlobalContext
. Since, Speaker
component is consuming a separate context that is not updated, it will prevent all instances of Speaker
component from re-rendering when any button is clicked.
const GlobalContext = React.createContext({});
const GlobalProvider = ({ children }) => {
const [speakerList, setSpeakerList] = React.useState([
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true }
]);
return (
<GlobalContext.Provider value={{ speakerList, setSpeakerList }}>
{children}
</GlobalContext.Provider>
);
};
const ClickFuncContext = React.createContext();
const ClickFuncProvider = ({ children }) => {
const { speakerList, setSpeakerList } = React.useContext(GlobalContext);
const clickFunction = React.useCallback(speakerIdClicked => {
setSpeakerList(currentState => {
return currentState.map(rec => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
});
});
}, []);
return (
<ClickFuncContext.Provider value={clickFunction}>
{children}
</ClickFuncContext.Provider>
);
};
const Speaker = React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const clickFunction = React.useContext(ClickFuncContext)
return (
<div>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</div>
);
});
function SpeakerList() {
const { speakerList } = React.useContext(GlobalContext);
return (
<div>
{speakerList.map(rec => {
return (
<Speaker speaker={rec} key={rec.id} />
);
})}
</div>
);
};
function App() {
return (
<GlobalProvider>
<ClickFuncProvider>
<SpeakerList />
</ClickFuncProvider>
</GlobalProvider>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can also see this demo on StackBlitz
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With