Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Stepper] Document how to avoid re-mounting between each step #22534

Closed
alehechka opened this issue Sep 9, 2020 · 21 comments · Fixed by #24292
Closed

[Stepper] Document how to avoid re-mounting between each step #22534

alehechka opened this issue Sep 9, 2020 · 21 comments · Fixed by #24292
Labels
component: stepper This is the name of the generic UI component, not the React module! docs Improvements or additions to the documentation good first issue Great for first contributions. Enable to learn the contribution process. performance

Comments

@alehechka
Copy link

In my application, I was rendering two different material-tables in two different steps of the Stepper component. When trying to test switching between steps quickly it would begin slowing down considerably and eventually freeze (in which I had to close the browser tab to continue).

I'm not sure the process for contributing code so I'm attaching below how I went around this by instead setting display: none; on all non-active steps.

interface StepContentProps {
	component: any;
	props?: any;
}

interface ContentProps {
	stepContent: StepContentProps[];
	activeStep: number;
}

const StepContent = ({ stepContent, activeStep }: ContentProps) => {
	return (
		<>
			{stepContent.map(({ component: Component, props }, index) => {
				return (
					<Component key={index} style={{ display: activeStep === index ? 'inherit' : 'none' }} {...props} />
				);
			})}
		</>
	);
};

Here's an example of what the array of StepContentProps looks like:

const stepContent: StepContentProps[] = [
		{
			component: MaterialTable,
			props: {
				title: 'Users',
				columns: userColumns as Column<User | Inventory>[],
				data: users,
				options: { selection: true, filtering: true, tableLayout: 'auto' },
				onSelectionChange: (rows) => {
					setSelectedUsers(rows);
				},
			} as MaterialTableProps<User>,
		}
]
@alehechka alehechka added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Sep 9, 2020
@oliviertassinari oliviertassinari added status: waiting for author Issue with insufficient information and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Sep 9, 2020
@oliviertassinari
Copy link
Member

oliviertassinari commented Sep 9, 2020

@alehechka What makes you believe that the issue is on the Material-UI side? What's happening if you replace material-table with the work in progress data grid?

Please provide a minimal reproduction test case. This would help a lot 👷 .
A live example would be perfect. This codesandbox.io template may be a good starting point. Thank you!

@doodhJalebi
Copy link

@alehechka I'm working on something that also requires the stepper to stop unmounting the when it is inactive. Can you please tell me what you changed and where you changed it for your solution to work? I'm not familiar with TS but I can try.

@oliviertassinari
Copy link
Member

oliviertassinari commented Sep 10, 2020

@doodhJalebi Thanks for the question, now I think I understand what problem @alehechka was facing. What about we update the documentation to include: https://material-ui.com/components/accordion/#performance. I believe the problem is the same. We can leverage the same solution: add the same section in the documentation.

@oliviertassinari oliviertassinari added component: stepper This is the name of the generic UI component, not the React module! docs Improvements or additions to the documentation ready to take Help wanted. Guidance available. There is a high chance the change will be accepted performance and removed status: waiting for author Issue with insufficient information labels Sep 10, 2020
@oliviertassinari oliviertassinari changed the title Issue with Stepper re-mounting components for each step. [Stepper] Document how to avoid re-mounting between each step Sep 10, 2020
@doodhJalebi
Copy link

@oliviertassinari that would be just perfect! Quick question: would this update to the docs be a patch or will I have to wait till v5 comes out?

@oliviertassinari
Copy link
Member

@doodhJalebi This has been supported since v1. The documentation would be on v5 (next branch). Do you want to give it a try? :)

@doodhJalebi
Copy link

@oliviertassinari I would love to give it a try (only if its not too much of a hassle for you).

@alehechka
Copy link
Author

@oliviertassinari Thanks for updating this to be a doc related issue, I think that's definitely the route to go since I don't think this is the fault of material-ui. I was originally following the Stepper docs here.

In the example code it uses the function getStepContent that will return the text to be used on that step. I changed that up a bit to instead supply the actual material-table component to be rendered. But due to the intensity of re-mounting the material-table so often is what caused the slow down. My solution provided above was to instead keep all components mounted but hidden when its step is not active.

@oliviertassinari
Copy link
Member

@doodhJalebi Go ahead, it will save us time. Note that the tradeoff of the Stepper is opposite to the Accordion. We don't mount all the steps to speed up the initial mount.

@doodhJalebi
Copy link

@oliviertassinari sorry for going AWOL. Um, noob questions but uh when you say "go ahead" or "give it a try", where do I go exactly? I apologize for my confusion 😕

@oliviertassinari
Copy link
Member

@doodhJalebi I was referring to opening a pull request. However, looking back at the problem, it doesn't seem to be a frequent request. I'm closing. An answer on GitHub' issue is already something developers can search and find (if they spend enough time)

@kevinvugts
Copy link

hi @oliviertassinari

Is it right that material-ui library re-mounts every step inside the stepper when switching back and forward between the steps? If so, how could I prevent this functionality?

Currently I am experiencing issues with the stat being reset to default on switching back and forward. Also with an instance of formik and formik persists after going back and forward twice, the formik persist state is also reset to defaults.

Looking forward for your answer.

Thanks!

@oliviertassinari oliviertassinari removed the ready to take Help wanted. Guidance available. There is a high chance the change will be accepted label Nov 3, 2020
@oliviertassinari
Copy link
Member

@kevinvugts Are you using the vertical stepper? I think that the behavior is correct. You will find #10569 for developers advocating for the current behavior. I'm reopening as, after all, we might benefit from documentation about it. The following should be enough:

diff --git a/docs/src/pages/components/steppers/steppers.md b/docs/src/pages/components/steppers/steppers.md
index 34004243f6..25a8c6c373 100644
--- a/docs/src/pages/components/steppers/steppers.md
+++ b/docs/src/pages/components/steppers/steppers.md
@@ -70,6 +70,16 @@ Vertical steppers are designed for narrow screen sizes. They are ideal for mobil

 {{"demo": "pages/components/steppers/VerticalLinearStepper.js"}}

+### Performance
+
+The content of a step is unmounted when closed.
+If you need to make the content available to search engines or render expensive component trees inside your modal while optimizing for interaction responsiveness
+it might be a good idea to keep the step mounted with:
+
+```jsx
+<StepContent TransitionProps={{ unmountOnExit: false }} />
+```
+
 ## Mobile stepper

 This component implements a compact stepper suitable for a mobile device. It has more limited functionality than the vertical stepper. See [mobile steps](https://material.io/archive/guidelines/components/steppers.html#steppers-types-of-steps) for its inspiration.

What do you think about it? Do you want to open a pull request?

@oliviertassinari oliviertassinari added the good first issue Great for first contributions. Enable to learn the contribution process. label Nov 3, 2020
@kevinvugts
Copy link

+it might be a good idea to keep the step mounted with:

Hi @oliviertassinari

Thanks for your prompt reply!

I am using the Horizontal Stepper at the moment. Does this component also has access to the TransitionProps?
TransitionProps={{ unmountOnExit: false }}

@oliviertassinari
Copy link
Member

@kevinvugts The horizontal stepper doesn't have this notion, so I think that you are looking at the wrong place.

@kevinvugts
Copy link

@kevinvugts The horizontal stepper doesn't have this notion, so I think that you are looking at the wrong place.

What would be the solution for the Horizontal Stepper? Is there a way I could prevent the unmounting components?

@kevinvugts

This comment has been minimized.

@oliviertassinari

This comment has been minimized.

@vicasas
Copy link
Member

vicasas commented Jan 6, 2021

I'll take this, if no one is working on it.

@nico-hernandez
Copy link

@kevinvugts The horizontal stepper doesn't have this notion, so I think that you are looking at the wrong place.

What would be the solution for the Horizontal Stepper? Is there a way I could prevent the unmounting components?

Did you get any solution? I've been trying with vertical and horizontal stepper and TransitionProps={{ unmountOnExit: false }} but it doesn't work, content is unmounted and mounted back, and selections/input data are no preserved.

@ils-juhis
Copy link

ils-juhis commented Dec 9, 2023

@kevinvugts The horizontal stepper doesn't have this notion, so I think that you are looking at the wrong place.

What would be the solution for the Horizontal Stepper? Is there a way I could prevent the unmounting components?

Did you get any solution? I've been trying with vertical and horizontal stepper and TransitionProps={{ unmountOnExit: false }} but it doesn't work, content is unmounted and mounted back, and selections/input data are no preserved.

Have you got any solution?

@vasanvijay
Copy link

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

  • Customizable Styles: Choose between different connector styles and customize the appearance of each step with different colors.
  • Interactive Steps: Click on steps to open a modal for additional actions or information.
  • Tooltips: Hover over steps to see tooltips with descriptive information.

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;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: stepper This is the name of the generic UI component, not the React module! docs Improvements or additions to the documentation good first issue Great for first contributions. Enable to learn the contribution process. performance
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants