I have a progress bar component in my React project that will run a "fill" animation with useEffect
every time it is mounted. The problem is that, higher up the component tree, there is a drawer component that will trigger a state change by toggling its open and close property. This causes the animation to run whenever the drawer is toggled due to a re-render and I want to prevent that.
Since the props for this component do not change, I read that re-renders can be prevented by passing my component in the memo
function. I also read that memo
only performs a shallow comparison, which is why I defined my own comparison function as such:
const areEqual = (prev: Readonly<ProgressBarProps>,
cur: Readonly<ProgressBarProps>) => {
console.log("check?");
if (typeof prev.fillType === "string" && typeof cur.fillType === "string") {
return prev.fillType === cur.fillType;
} else if (
typeof prev.fillType !== "string" &&
"from" in prev.fillType &&
typeof cur.fillType !== "string" &&
"from" in cur.fillType
) {
return prev.fillType.from === cur.fillType.from && prev.fillType.to === cur.fillType.to;
} else {
return false;
}
}
const MemoizedProgressBar = memo(ProgressBar, areEqual);
export default MemoizedProgressBar;
The console.log
statement is not executed, which led me to suspect the areEqual
function is not running. Does this mean the memo
function is not running as well? How can I prevent needless re-renders everytime I toggle the drawer component? This is the full code for the progress bar component:
ProgressBar.tsx
import { Box, SxProps } from "@mui/material";
import { memo, useEffect, useRef } from "react";
type monotone = string;
type gradient = {
from: string;
to: string;
};
type ProgressBarProps = {
fillType: monotone | gradient;
};
const ProgressBar = ({ fillType }: ProgressBarProps) => {
const progressFillRef = useRef<HTMLDivElement>();
useEffect(() => {
if (progressFillRef !== null) {
setTimeout(() => {
progressFillRef.current!.style.width = "50%";
}, 1000);
}
}, []);
const progressFillStyles = {
height: "100%",
transition: "width 0.5s cubic-bezier(.26,.8,.68,1.07)",
borderRadius: "inherit",
width: 0,
} satisfies SxProps;
const getFillType = () => {
if (typeof fillType === "string") {
return { backgroundColor: fillType, ...progressFillStyles };
}
return "from" in fillType
? {
backgroundImage: `linear-gradient(to left top, ${fillType.from}, ${fillType.to})`,
...progressFillStyles,
}
: { backgroundColor: "green", ...progressFillStyles };
};
return (
<Box
sx={{
width: "100%",
maxWidth: "700px",
height: "15px",
backgroundColor: "#ddd",
borderRadius: "10px",
}}
>
<Box ref={progressFillRef} sx={getFillType()} />
</Box>
);
};
const areEqual = (prev: Readonly<ProgressBarProps>, cur: Readonly<ProgressBarProps>) => {
console.log("check?");
if (typeof prev.fillType === "string" && typeof cur.fillType === "string") {
return prev.fillType === cur.fillType;
} else if (
typeof prev.fillType !== "string" &&
"from" in prev.fillType &&
typeof cur.fillType !== "string" &&
"from" in cur.fillType
) {
return prev.fillType.from === cur.fillType.from && prev.fillType.to === cur.fillType.to;
} else {
return false;
}
}
const MemoizedProgressBar = memo(ProgressBar, areEqual);
export default MemoizedProgressBar;