ProgressBar Component Needlessly Rerendering on Parent State Change

102 Views Asked by At

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;

0

There are 0 best solutions below