I'm trying to do a memoize Modal
and I have a problem here.
When I change input I dont need to re-render the Modal component.
For example:
Modal.tsx
looks like this:
import React from "react";
import { StyledModalContent, StyledModalWrapper, AbsoluteCenter } from "../../css";
interface ModalProps {
open: boolean;
onClose: () => void;
children: React.ReactNode
};
const ModalView: React.FC<ModalProps> = ({ open, onClose, children }) => {
console.log("modal rendered");
return (
<StyledModalWrapper style={{ textAlign: "center", display: open ? "block" : "none" }}>
<AbsoluteCenter>
<StyledModalContent>
<button
style={{
position: "absolute",
cursor: "pointer",
top: -10,
right: -10,
width: 40,
height: 40,
border: 'none',
boxShadow: '0 10px 10px 0 rgba(0, 0, 0, 0.07)',
backgroundColor: '#ffffff',
borderRadius: 20,
color: '#ba3c4d',
fontSize: 18
}}
onClick={onClose}
>
X
</button>
{open && children}
</StyledModalContent>
</AbsoluteCenter>
</StyledModalWrapper>
);
};
export default React.memo(ModalView);
Here is an example of how I wrap it.
import React from 'react'
import Modal from './modal';
const App: React.FC<any> = (props: any) => {
const [test, setTest] = React.useState("");
const [openCreateChannelDialog, setOpenCreateChannelDialog] = React.useState(false);
const hideCreateModalDialog = React.useCallback(() => {
setOpenCreateChannelDialog(false);
}, []);
return (
<>
<input type="text" value={test} onChange={(e) => setTest(e.target.value)} />
<button onClick={() => setOpenCreateChannelDialog(true)}>Create channel</button>
<Modal
open={openCreateChannelDialog}
onClose={hideCreateModalDialog}
children={<CreateChannel onClose={hideCreateModalDialog} />}
/>
</>
};
I know, Modal
re-rendered because children
reference created every time when App
component re-renders (when I change an input text).
Know I'm interested, if I wrap <CreateChannel onClose={hideCreateModalDialog} />
inside React.useMemo() hook
For example:
const MemoizedCreateChannel = React.useMemo(() => {
return <CreateChannel onClose={hideCreateModalDialog} />
}, [hideCreateModalDialog]);
And change children props inside Modal
from:
children={<CreateChannel onClose={hideCreateModalDialog} />}
to
children={MemoizedCreateChannel}
It works fine, but is it safe? And it is only one solution that tried to memoize a Modal?
We can also wrap the return value of our functional component in a useMemo callback to memoize, the component would re-render but the return value will be based on its dependencies, if changed will return a new value, if not will return cached version. The return value is a JSX that React uses to create a virt.
According to React documentation, useMemo should be used to optimize performance, not to store data. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components.
React has a built-in hook called useMemo that allows you to memoize expensive functions so that you can avoid calling them on every render. You simple pass in a function and an array of inputs and useMemo will only recompute the memoized value when one of the inputs has changed.
We should also not use useMemo when the function returns a primitive value, such as a boolean or a string. Because primitive values are passed by value, not by reference, it means that they always remain the same, even if the component is re-rendered.
UseMemo is one of the hooks offered by React. This hook allows developers to cache the value of a variable along with a dependency list. If any variables in this dependency list changes, React will re-run the processing for this data and re-cache it.
Another and the most obvious situation in which useMemo probably does more harm than good is when the function being memoized does not perform expensive operations. useMemo itself requires memory so if we are trying to over-optimize by memoizing every function, it might slow the application down.
If you’ve used Hooks in any serious production app, then you’ve likely been tempted to use the useMemo Hook in one of these two categories. I’ll show you why these are unimportant and likely hurting the performance of your application, and more interestingly, I’ll show you my recommendations on how not to overuse useMemo in these use cases.
Memoizing JSX expressions is part of the official useMemo
API:
const Parent = ({ a }) => useMemo(() => <Child1 a={a} />, [a]);
// This is perfectly fine; Child re-renders only, if `a` changes
useMemo
memoizes individual children and computed values, given any dependencies. You can think of memo
as a shortcut of useMemo
for the whole component, that compares all props.
But memo
has one flaw - it doesn't work with children:
const Modal = React.memo(ModalView);
// React.memo won't prevent any re-renders here
<Modal>
<CreateChannel />
</Modal>
children
are part of the props. And React.createElement
always creates a new immutable object reference (REPL). So each time memo
compares props, it will determine that children
reference has changed, if not a primitive.
To prevent this, you can either use useMemo
in parent App
to memoize children
(which you already did). Or define a custom comparison function for memo
, so Modal
component now becomes responsible for performance optimization itself. react-fast-compare
is a handy library to avoid boiler plate for areEqual
.
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