mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.69k stars 32.23k forks source link

[Stepper] Unnecessary rerender with StepContent #30811

Open nosyn opened 2 years ago

nosyn commented 2 years ago

Duplicates

Latest version

Current behavior 😯

I'm trying to combine the Vertical Stepper Component with the components that I already had. Something weird happens that whenever I go to the next step, the component inside the <StepContent> render twice and it's not happening the first time the component is rendered.

Expected behavior 🤔

Better render performance with children elements inside the <StepContent>

Steps to reproduce 🕹

Codesanbox: https://codesandbox.io/s/verticallinearstepper-material-demo-forked-ossb3?file=/demo.js

Steps:

  1. See render Test Component from the console on the first time the web app is loaded image

  2. Click Continue

  3. See render Test Component from the console is called twice this time image

Context 🔦

No response

Your environment 🌎

`npx @mui/envinfo` ``` Don't forget to mention which browser you used. Output from `npx @mui/envinfo` goes here. ```
webxys commented 2 years ago

same problem here, looking for the solution

nico-hernandez commented 2 years ago

Did you get any solution?

fsoubes commented 1 year ago

Someone found a solution please ?

nrathi commented 1 year ago

+1 on this.

jacobgawel commented 9 months ago

Yup, gonna have to figure out a different way to get input because mui stepper keeps re-rendering and the input keeps losing focus. Cant believe this is still an issue after 2 years lol.

vasanvijay commented 2 months ago

CustomStepper Component

Overview

The CustomStepper component is a versatile stepper UI built with Material-UI (MUI) that allows you to create a dynamic and interactive step-by-step navigation system. It supports two styles of connectors (qonto and colorlib), custom icons for each step, and interactive modals for additional actions.

Features

Static Array Issue and Solution

Problem: When the steps in the stepper are static (i.e., not changing dynamically), the component may re-render more often than necessary, which can affect performance.

Solution: To optimize performance, use a static array for steps instead of dynamically generating it. This approach prevents unnecessary re-renders and improves efficiency.

Implementation Steps

  1. Define a Static Array for Steps Define the array of steps outside the component to ensure it remains constant and does not trigger re-renders.

    const stepsMore = ["D", "O", "E", "R", "-R"];
  2. Update the Component Use the static array inside the component to render the steps.

    const CustomStepper = React.memo(function CustomStepper({
     steps,
     activeStep,
     connectorType = "qonto",
     icons,
     highlightStep,
     label_status,
     HandleAddRating,
     stepColors,
    }) {
     // Static steps array
     const stepsMore = ["D", "O", "E", "R", "-R"];
    
     // ... Rest of the component code
    });
  3. Optimize Rendering Ensure that the Stepper component only re-renders when necessary by using React.memo and optimizing prop comparisons.

Full Component Code

import React, { useCallback, useMemo, useState } from "react";
import { styled } from "@mui/material/styles";
import Stack from "@mui/material/Stack";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Check from "@mui/icons-material/Check";
import StepConnector, { stepConnectorClasses } from "@mui/material/StepConnector";
import DropdownModal from "../DropdownModal/DropdownModal";
import Tooltip from "@mui/material/Tooltip";

// Styles for connectors and step icons
// (Styles as provided above)

// Component definition
const CustomStepper = React.memo(function CustomStepper({
  steps,
  activeStep,
  connectorType = "qonto",
  icons,
  highlightStep,
  label_status,
  HandleAddRating,
  stepColors,
}) {
  const Connector = useMemo(
    () => (connectorType === "colorlib" ? ColorlibConnector : QontoConnector),
    [connectorType]
  );

  const StepIconComponent = useMemo(
    () => (connectorType === "colorlib" ? ColorlibStepIcon : QontoStepIcon),
    [connectorType]
  );

  const [modalOpen, setModalOpen] = useState(false);
  const [selectedIcon, setSelectedIcon] = useState("D");

  const handleStepClick = useCallback(
    (icon, index) => {
      setSelectedIcon(icon);
      if (stepColors[index] !== "#4caf50") {
        setModalOpen(true);
      }
    },
    [stepColors]
  );

  const handleClose = useCallback(() => setModalOpen(false), []);

  const allStepsCompleted = useMemo(
    () => activeStep >= steps.length,
    [activeStep, steps.length]
  );

  const handleSaveRating = useCallback(
    (ratingObject) => {
      HandleAddRating(ratingObject);
      setModalOpen(false);
    },
    [HandleAddRating]
  );

  const stepsMore = ["D", "O", "E", "R", "-R"];

  return (
    <>
      {connectorType === "colorlib" ? (
        <ColorlibGlobalStyles />
      ) : (
        <GlobalStyles />
      )}
      <Stack sx={{ width: "100%" }} spacing={4}>
        <Stepper
          alternativeLabel
          activeStep={activeStep}
          connector={<Connector stepColor={stepColors[activeStep]} />}
        >
          {stepsMore.map((label, index) => (
            <Step key={label}>
              <Tooltip title={steps[index]} arrow>
                <span>
                  <StepLabel
                    StepIconComponent={(props) => (
                      <StepIconComponent
                        {...props}
                        icon={icons ? icons[index + 1] : undefined}
                        onClick={() =>
                          handleStepClick(icons[index + 1], index)
                        }
                        highlight={highlightStep === index}
                        stepColor={stepColors[index]}
                        allCompleted={allStepsCompleted}
                      />
                    )}
                  >
                    <span>{label}</span>
                  </StepLabel>
                </span>
              </Tooltip>
            </Step>
          ))}
        </Stepper>
      </Stack>
      <DropdownModal
        open={modalOpen}
        handleClose={handleClose}
        selectedIcon={selectedIcon}
        HandleAddRating={handleSaveRating}
        step_name={steps[activeStep]}
        label_status={label_status}
      />
    </>
  );
}, (prevProps, nextProps) => {
  return (
    prevProps.activeStep === nextProps.activeStep &&
    prevProps.connectorType === nextProps.connectorType &&
    prevProps.highlightStep === nextProps.highlightStep &&
    prevProps.label_status === nextProps.label_status &&
    JSON.stringify(prevProps.steps) === JSON.stringify(nextProps.steps) &&
    JSON.stringify(prevProps.stepColors) ===
      JSON.stringify(nextProps.stepColors)
  );
});

export default CustomStepper;