mui / material-ui

Material UI: Ready-to-use foundational React components, free forever. It includes Material UI, which implements Google's Material Design.
https://mui.com/material-ui/
MIT License
92.04k stars 31.64k forks source link

Improve Material-UI performance #10778

Closed Bessonov closed 4 years ago

Bessonov commented 6 years ago

First, thank you very much for this awesome component library! It's great!

I added a drawer in my new app. Mostly, I copypasted drawer example. Just for PoC I multiplied

        <Divider />
        <List>{mailFolderListItems}</List>

section.

After that it feels very slow, especially on mobile device (nexus 4, cordova with crosswalk 20). I use dev mode, but prod mode doesn't speed up much.

Through react dev tools I noticed that components in mailFolderListItems rendered on every link click in react-router or even menu open. It takes sometime up to 50-60ms to rerender ONE {mailFolderListItems}. I use

const modalProps = {
    keepMounted: true, // Better open performance on mobile.
};

To eliminate uncertainty with other app components, I converted mailFolderListItems to Component and disable rerendering:

class MailFolderListItems extends React.Component<{}, {}> {

    shouldComponentUpdate() {
        return false;
    }

    render() {
        return (
            <List>
                <Link to={Routes.Startpage.path}>
                    <ListItem>
                        <ListItemIcon>
                            <InboxIcon />
                        </ListItemIcon>
[...]

                <Divider />
                <MailFolderListItems />

After that this part feels OK.

I found https://github.com/mui-org/material-ui/issues/5628 . I suggest to revisit it. Optimizing shouldComponentUpdate is fundamental and most important step to gain performance. PureComponent is just most common shortcut.

Furthermore, I noticed that very much time (1-2ms and more for EVERY material-ui component) is spended in WithStyles.

Expected Behavior

I'm expecting to get most of possible react performance for this great library.

Current Behavior

The app get slower with every material-ui component.

Steps to Reproduce (for bugs)

I don't provide reproducing example yet, because I just copypasted from component demo page, but if needed I can provide codesandbox demo. For browser it's noticeable, if browser slowed down by factor >=5x in performance setting.

Your Environment

Tech Version
Material-UI 1.0.0-beta.38
Material-UI Icons 1.0.0-beta.36
React 16.2.0
browser cordova crosswalk 20 (equals android chrome 50)
eps1lon commented 5 years ago

@danielo515 Hard to tell without the full example. The List components are also using context so if you're changing that value too it will also trigger rerenders.

danielo515 commented 5 years ago

I understand that @eps1lon. However, could you please explain how a change in context can happen? I am not passing anything to the list component, just rendering a lost of children inside it.

eps1lon commented 5 years ago

@danielo515 Basically every time the List rerenders. The new context API enabled some optimization strategies we could explore by memoizing the context value.

Currently reacts context API triggers a rerender on every consumer if the the value changes WRT to strict equality. Since we create a new object in every render call to List every consumer will also rerender.

So for now an easy perf boost would be to wrap the List so that doesn't rerender as frequently. I would recommend doing benchmarks first though or bail out of render earlier. Repeated render calls Typography shouldn't be that bad.

prevostc commented 5 years ago

Wrapping components with React.memo works well for anything without children. I have a form with 3 ExpansionPanel and some 14 FormControl and it's lagging on desktop.

Without a solution for these performance issues, I will not be able to keep using material-ui :s

oliviertassinari commented 5 years ago

@prevostc it's hard to tell what's wrong without an example.

kof commented 5 years ago

@prevostc without seeing a codesandbox reproduction, neither us nor you can know if this is even related to this issue

dantman commented 5 years ago

@prevostc Create your own components that do not require children which you can then memoize.

i.e. Create your own pure/memoed MyExpansionPanel component that takes data/event props but not children and is responsible for rendering a single expansion panel. Then use that <MyExpansionPanel ... /> to render each of your expansion panels. Then re-renders will be limited to a single expansion panel (or two when transitioning between two).

prevostc commented 5 years ago

@oliviertassinari @kof @dantman Here is a codesandbox reproduction of my performance issue: https://codesandbox.io/s/yvv2y2zxxx

It is a form with ~20 fields (not uncommon) you will experience some lag on user input. On slower devices, this form is just not usable.

The performance issue comes from the massive re-render on user input, but wrapping MUI components in a pure component (React.memo) does nothing as everything here has children and children this will force re-rendering AFAIK (source: https://reactjs.org/docs/react-api.html#reactpurecomponent)

Below are some screenshots of my manual benchmark, one without any optimisation, one with everything memoized and one using a custom input component with a local state to avoid setting the state on whole form too often. On each configuration, a user input reconciliation takes around 60ms (way above the 16ms I need to render at 60fps.

Please note that I would gladly learn that I did something wrong because I love MUI and I would be sad if there was not an easy fix <3

@dantman How can you write an ExpansionPanel that do not take any React.ReactNode as input (children or props)? If you mean that I should write a specific component for every panel in my app, this is not possible unfortunately, I have too many of them.

screenshot 2018-12-20 at 22 04 08 screenshot 2018-12-20 at 21 56 57 screenshot 2018-12-20 at 22 05 00
dantman commented 5 years ago

@dantman How can you write an ExpansionPanel that do not take any React.ReactNode as input (children or props)? If you mean that I should write a specific component for every panel in my app, this is not possible unfortunately, I have too many of them.

Yes, instead of making a single massive component with a deeply nested tree of panels separate the expansion panel pieces out into components. It is impossible to optimize massive trees like that.

It's not impossible. React components are very lightweight. Copy and paste a block of code from a massive component into a function and you're almost done, after that you just have to hookup the props. And as long as you React.memo that function and avoid passing things that break the pure optimization of the props, then things are optimized pretty easily.

Remember, if you are creating a small component to break a chunk out of a huge component. It does not have to be something complex with its own file and own prop validation. It can be a simple functional component in the same file as the component it is used in with no propTypes. i.e. You can create small components that are only available to the component in the same file as them.

Yes, Material UI is a bit slower than low level dom elements. But even if a MUI Input were 10x slower than a raw input and things became too slow after 100. Then you still have a problem even without MUI because even if it's 10x faster the raw input would make the site equally slow if you had 1000 of them. You cannot use single monolithic components in React even when you are aren't using MUI. You need to break your app up into reasonably sized chunks so that React has defined boundaries it can optimize.

dantman commented 5 years ago

@prevostc Here's your demo optimized by splitting the expansion panel out into a tiny same-file component. As you can see only one expansion panel is re-rendered when an input is updated. Time is not wasted re-rendering unrelated expansion panels. If I wanted I could even do something similar to inputs that share a similar pattern, resulting in unrelated inputs also not re-rendering.

I will note that this is not just a good optimization pattern, it's a good coding pattern. In real code, where you are not making an array of inputs but explicitly writing them out with defined names, labels, and purposes. Finding boundaries and repeating patterns not only optimizes things but also reduces boilerplate making code more DRY and readable. i.e. Instead of InputWrapper I would probably break that FormControl+InputLabel+FormHelperText+Input combo into a small local SimpleTextInput component. Which would not just optimize it (resulting in unrelated inputs not re-rendering) but would also mean that the code does not need to repeat the extra boilerplate.

https://codesandbox.io/s/0o7vw76wzp

screen shot 2018-12-20 at 2 51 31 pm
danielo515 commented 5 years ago

Reading this I came to the conclusion that in order to optimize mui you need to create smaller specific components. That is something I already realized and tried with success. However, I also did understand that there is no way to optimize list components because the context api changes all the input props.

Regards

prevostc commented 5 years ago

Ok here is an updated stress test https://codesandbox.io/s/wz7yy1kvqk

I agree with @dantman on this generic point https://github.com/mui-org/material-ui/issues/10778#issuecomment-449153635 BUT I was not expecting such performance issue with this low amount of components, but bear with me as I found the source of my performance issue.

Is JSS slow ? (spoiler alert: no)

In reference to some of the earlier comments in this thread, I added a checkbox in the stress test to remove all calls to withStyles and I came to the conclusion that JSS is fast and it's not the source of the perf issue (as @kof pointed it out in https://github.com/mui-org/material-ui/issues/10778#issuecomment-396609276).

screenshot 2018-12-22 at 15 17 26

The fix

For my specific use case, I was able pinpoint the issue being that each form input was re-rendered on form update, even though only one input actually changed. In the screenshot below, I wrapped both the FormControl and the Input in a memoized component, avoiding render if the value did not change. @dantman actually suggested that I create a specific component for each ExpansionPanel but this is a way less generic solution. As a side note, each panel is still re-rendered and performance is far from optimal but it's sufficient for now.

screenshot 2018-12-22 at 15 18 22

So? What's next?

I think that there is no way to avoid this kind of issues with a change to the material-ui code without a massive change to the current API heavily relying on composition of React.ReactNode. But as @dantman mentioned in https://github.com/mui-org/material-ui/issues/10778#issuecomment-449153635, MUI is a bit slower that what it expected from it. Not addressing this at all is a mistake IMHO.

Being aware of this issue, we may need to create a doc page related to performance issues and how to address them. Even if his page will mainly redirect to the official doc (https://reactjs.org/docs/optimizing-performance.html) and list components that might cause perf issues, it's a start. It would be better to console.warn the user when such issue occur but I can't figure out a way to detect issues at the material-ui level.

kof commented 5 years ago

@prevostc this message made my day, this is the kind of community I love. Do you have ideas what mui could change to make performance better and avoid the need for user land optimizations? An API change might be doable.

prevostc commented 5 years ago

I do not :s

I do not know MUI's internal enough to have any idea how to improve it's raw performance (without api change) right now. I'm working on some ideas but nothing conclusive as of today: I have an app where a radio group is re-renderer when it's direct parent is not, cannot re-produce this locally yet.

Any API change would be to consider removing any React.ReactNode props from the API (children and other props like icon, etc) but I could not find a good way to keep the same configurability. Here is what I tried: https://codesandbox.io/s/jpw36jw65 (warning: not finished).

Also it is something to note that MUI is especially slow while on development mode due to react being especially slow in development mode. I don't know if there is a way to improve on this.

mkermani144 commented 5 years ago

Is there any progress on adding capabilities (like the one @Bessonov suggested) in order to optimize the performance issues Material-UI currently faces?
When we started using this great library for our project, I didn't know such performance issues may happen when the project gets larger and larger; in addition, I didn't see any section in Material-UI docs informing me about the cases that may cause Material-UI to become slow and harm UX.
Lots of performance problems - directly or indirectly related to Material-UI - are reported in this issue. I think it will be a good idea to list them in another issue so that everyone can track the progress. If you think it's OK, I can open a new issue.

oliviertassinari commented 5 years ago

@mkermani144 We have yet to see a performance report directly correlated to something Material-UI is doing wrong. So far this issue has been used as a help forum for people struggling. Do you confirm that nothing actionable was reported? I'm going to state the obvious, React abstraction has a cost. Each component wrapping a host will add weight to your render tree, it slows it down. While you can render more than 100 list items with native host, it starts to be an issue when you wrap them with a class component. It's not specific to Material-UI.

Table

Dev mode

Let's take the example of a table. It's a component people find slow. We have documented the virtualization, it helps, a lot.

In the following test case we render 100 items in dev mode. We can consider the following cases:

  1. Table Raw: https://codesandbox.io/s/v066y5q7z3: 63ms in the render
  2. Table Material-UI Master: https://codesandbox.io/s/m4kwmvj9ly: 250ms in the render
  3. Table Material-UI Next: https://codesandbox.io/s/2o35yny1jn: 262ms in the render

So the overhead of using Material-UI in dev mode over host element is around x4 (the difference is smaller in production!), simply because we create intermediary components. It's why virtualization starts to be important after render a list of ~100 table items. Now, we can dive a bit into Why and what can we do about it?

  1. Table Raw + function component. Why a functional component? We want to abstract the class names used. We don't want the component users to repeat them. https://codesandbox.io/s/1zl75mwlpj: 105ms in the render
  2. Table Raw + function component + forwardRef. Why forwardRef? We want the component to be "transparent", being able to access the host element with a ref. https://codesandbox.io/s/32o2y0o9op: 120ms in the render
  3. Table Raw + function component + forwardRef + withStyles. Why withStyles? Because we want to style our component: https://codesandbox.io/s/j2n6pv768y: 200ms in the render
  4. Table Raw + function component + forwardRef + makeStyles. makeStyles might be faster than withStyles, let's try: https://codesandbox.io/s/yw52n07l3z: 130ms in the render.

So, we have one leverage available: migrate all the component from withStyles to makeStyles. We can win about +30% of performance (262 / (262 - 70)) in dev mode.

Production mode

I have run the same test cases in production mode:

So the withStyles to makeStyles migratation is still a +30% speedup in theory in production mode.

oliviertassinari commented 5 years ago

If you think it's OK, I can open a new issue.

@mkermani144 If you have a specific case were Material-UI is doing it wrong, sure.

mkermani144 commented 5 years ago

I read about all of the comments below this issue. My problem doesn't fit in any of the other ones mentioned earlier.

I have a List component containing some ListItems, having one of them selected and highlighted. When I click another ListItem to select and highlight it, the whole list (containing its children) gets re-rendered again.

I know that this problem may seem exactly the same as a previous comment, but it's not; at least I think so.

Let's look at the results from React profiler:

image As you can see, I have a MyList component at the top level of the image. This component is only a wrapper around MUI List and just makes it pure, that is:

class MyList extends React.PureComponent {
  render() {
    return (
      <List>
        {this.props.children}
      </List>
    );
  }
}

I added this wrapper because @eps1lon in one of his comments mentioned that re-rendering List causes context to update, and this context update makes all consumers (including ListItems) to re-render too.

I also tried making all my ListItems pure and profiled the app again. The results were the same, except that my custom component (i.e. MyListItem) didn't re-render itself, but all the children below it did.

I know the problem occurs because the context MUI uses for styling re-renders somehow. But I don't know why this re-render happen and how I can avoid it.

Or, am I doing something wrong?

Note: I use MUI new (alpha) styling solution, that is, @material-ui/styles. I don't know if this matters.

oliviertassinari commented 5 years ago

@mkermani144 Remove Material-UI with native elements, observe that the re-rendering is still present. The pure logic won't help like this. React.createElement creates new references at each render, it invalidates your PureComponent.

mkermani144 commented 5 years ago

Yes, I know elements are objects, and objects are not strictly equal in Javascript, so sCU fails.

But I don't understand what you mean when you say React.createElement is called again. Which call to createElement? If you mean the calls inside List, it only calls createElement for its children (ListItems) only if it is re-rendered. If it's not re-rendered, no createElement will be called and no re-render should happen. The problem is that the List itself gets re-rendered.

oliviertassinari commented 5 years ago

@mkermani144 If you can create a minimal reproduction example, we can have a look at it.

Pajn commented 5 years ago

Your MyList (and thus List) gets rerendered because of the component that renders MyList (lets call it MyComponent) gets rerendered. PureComponent on MyList does not help as MyComponent have been rerendered and created new children for MyList so that MyLists check fails.

Your MyComponent probably gets rerendered because there is where you store the state of which item is selected.

I think MUIs implementation of List should change to not recreate the List context value each render here: https://github.com/mui-org/material-ui/blob/fb4889f42613b05220c49f8fc361451066989328/packages/material-ui/src/List/List.js#L57

So instead have List look something like this:

const List = React.forwardRef(function List(props, ref) {
  const {
    children,
    classes,
    className,
    component: Component,
    dense,
    disablePadding,
    subheader,
    ...other
  } = props;
  const context = React.useMemo(() => ({ dense }), [dense]);

  return (
    <Component
      className={clsx(
        classes.root,
        {
          [classes.dense]: dense && !disablePadding,
          [classes.padding]: !disablePadding,
          [classes.subheader]: subheader,
        },
        className,
      )}
      ref={ref}
      {...other}
    >
      <ListContext.Provider value={context}>
        {subheader}
        {children}
      </ListContext.Provider>
    </Component>
  );
});

That would simplify creating sCU versions of the ListItems

mkermani144 commented 5 years ago

Your MyList (and thus List) gets rerendered because of the component that renders MyList (lets call it MyComponent) gets rerendered. PureComponent on MyList does not help as MyComponent have been rerendered and created new children for MyList so that MyLists check fails.

@Pajn No, look at my React profiler results. MyList didn't re-render (it's grey), but List did (it's blue). I don't persist on PureComponent for MyList. Even though I implement sCU for MyList so that it doesn't re-render, List does re-render.

mkermani144 commented 5 years ago

@oliviertassinari I created a minimal reproduction example:

import React, { Component } from 'react';

import StylesProvider from '@material-ui/styles/StylesProvider';
import ThemeProvider from '@material-ui/styles/ThemeProvider';

import { createMuiTheme } from '@material-ui/core';

import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';

const theme = createMuiTheme({});

const MyListItem = React.memo(ListItem, (prev, next) => prev.selected === next.selected);

class App extends Component {
  state = {
    selected: null,
  }
  render() {
    return (
      <StylesProvider>
        <ThemeProvider theme={theme}>
          <List>
            {[0, 1, 2, 3, 4].map(el => (
              <MyListItem
                button
                selected={el === this.state.selected}
                onClick={() => this.setState({ selected: el })}
              >
                {el}
              </MyListItem>
            ))}
          </List>
        </ThemeProvider>
      </StylesProvider>
    );
  }
}

export default App;

React profiler results (after clicking on the 4th list item):

image

As you can see, it works as expected, i.e. there is no extra re-renders (except for ButtonBase components inside ListItems). The problem is that, this reproduction is too minimal; there are lots of stuff i skip in it.

I know you cannot tell me what is wrong with my code causing extra re-renders. But I ask you a question: What can cause a re-render in the WithStylesInner components which wrap MUI components?

oliviertassinari commented 5 years ago

@mkermani144 What do you think of this fix?

--- a/packages/material-ui/src/List/List.js
+++ b/packages/material-ui/src/List/List.js
@@ -40,6 +40,13 @@ const List = React.forwardRef(function List(props, ref) {
     ...other
   } = props;

+  const context = React.useMemo(
+    () => ({
+      dense,
+    }),
+    [dense],
+  );
+
   return (
     <Component
       className={clsx(
@@ -54,7 +61,7 @@ const List = React.forwardRef(function List(props, ref) {
       ref={ref}
       {...other}
     >
-      <ListContext.Provider value={{ dense }}>
+      <ListContext.Provider value={context}>
         {subheader}
         {children}
       </ListContext.Provider>

Do you want to submit a pull request? :) We are using the same strategy with the Table component. It works great. Thank you for reporting the problem!

mkermani144 commented 5 years ago

@oliviertassinari Sure. This is exactly what @Pajn suggested earlier. I submitted a PR.

mkermani144 commented 5 years ago

14934 is merged; the List component, however, may become a performance bottleneck if it's large enough, no matter how much it's optimized. Shouldn't we provide an example showing the usage of react-window or react-virtualized with List component, like the one we have in Table docs?

eps1lon commented 5 years ago

Shouldn't we provide an example showing the usage of react-window or react-virtualized with List component, like the one we have in Table docs?

That'd be great :+1:

henrylearn2rock commented 5 years ago

Fwiw I built a chat app and the app needs to render a large list of contacts. I ran into the same issue @mkermani144 had.

https://stackoverflow.com/questions/55969987/why-do-the-children-nodes-rerender-when-the-parent-node-is-not-even-being-update/55971559

oliviertassinari commented 5 years ago

@henrylearn2rock Have you considered using virtualization? We have added a demo for the list: https://next.material-ui.com/demos/lists/#virtualized-list.

pytyl commented 5 years ago

This also really tripped me up. I think most people (including me) assumed everything under a pure component was safe from a rerender, which evidently isn't the case for this library. I am going to try virtualization as you most recently suggested. Thanks!

eps1lon commented 5 years ago

I think most people (including me) assumed everything under a pure component was safe from a rerender, which evidently isn't the case for this library.

This is not how React.PureComponent or React.memo works. It only affects the the component itself. Children might still have to re-render if context changes.

@pytyl Can you share the code where you used a PureComponent and expected it to prevent any re-render in its sub-tree?

pytyl commented 5 years ago

@eps1lon the following documentation makes it seem like returning false from shouldComponentUpdate automatically skips re-render in children components. https://reactjs.org/docs/optimizing-performance.html#shouldcomponentupdate-in-action

Since shouldComponentUpdate returned false for the subtree rooted at C2, React did not attempt to render C2, and thus didn’t even have to invoke shouldComponentUpdate on C4 and C5.

Maybe I am mistaken on this? The following is a snapshot of my profiler. Just for the sake of testing, I explicitly return false for shouldComponentUpdate in my Menu component:

Screen Shot 2019-05-08 at 7 46 32 PM

This made all of my children components (Categories, Category, CategoryItems, CategoryItem) not re-render. Many MUI related things appear to be re-rendering at the bottom which seems to be causing a lot of delay. Stuff like withStyles, Typography, ButtonBase. Still a bit new to React so please excuse my ignorance. Below is my code for Menu component (where I am returning false for shouldComponentUpdate):

import React, { Component } from "react";
import Categories from "./Categories";
import { withStyles, Paper } from "@material-ui/core";

const styles = theme => ({
  root: {
    paddingTop: 0,
    marginLeft: theme.spacing.unit * 2,
    marginRight: theme.spacing.unit * 2,
    marginTop: theme.spacing.unit * 1
  }
});

class Menu extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.categories.length == this.props.categories.length) {
      return false;
    }
    return true;
  }

  render() {
    const { classes, categories } = this.props;

    return (
      <Paper className={classes.root}>
        <Categories categories={categories} />
      </Paper>
    );
  }
}

export default withStyles(styles)(Menu);
eps1lon commented 5 years ago

I would need a full codesandbox to understand the issue.

pytyl commented 5 years ago

@eps1lon I will try to produce one for tomorrow. Thank you.

pytyl commented 5 years ago

@eps1lon here is the codepen: https://codesandbox.io/s/348kwwymj5

Short description It's a basic menu app for restaurants (which often have upwards of 100 menu items). When the user clicks on a menu item, it opens an "add to order" dialog. I will attach a few situations where the profiler is showing poor performance (these statistics are not on a production build).

System MacBook Pro (Retina, 13-inch, Early 2015) 3.1 GHz Intel Core i7 Firefox 66.0.3

Case 1 (user clicks on a menu item) Render duration: 218ms

Screen Shot 2019-05-10 at 4 45 26 AM Screen Shot 2019-05-10 at 4 45 48 AM

Case 2 (user clicks add to order button in dialog) Render duration: 356ms

Screen Shot 2019-05-10 at 4 46 24 AM Screen Shot 2019-05-10 at 4 47 10 AM

I'm sure I'm making some novice mistake here so any guidance is greatly appreciated.

Pajn commented 5 years ago

As WithStyles(ButtonBase) is rerendered I assume that WithStyles uses a context that is recreated even though it doesn't need to.

I managed to find this https://github.com/mui-org/material-ui/blob/048c9ced0258f38aa38d95d9f1cfa4c7b993a6a5/packages/material-ui-styles/src/StylesProvider/StylesProvider.js#L38 but can't find a place where StylesProvider is used in actual code (GitHubs search isn't very good) but it might be the reason.

Does @eps1lon know if this might be the cause? If it is, a useMemo on the context object would probably fix this. Though I don't know if the localOptions are stable or if useMemo needs to be propagated even further.

eps1lon commented 5 years ago

Maybe. But you should first investigate why your component using a StylesProvider rerenders. This is either something at the top of your tree or should be at some UI boundary. In either case those should rarely rerender. Remember that react context isn't optimized for frequent updates anyway.

We shouldn't prematurely optimize these things just because some object is re-created during render. Memoization is not a silver bullet. So without a concrete example I can't do much. Yes things re-render. Sometimes more often then they need to. But a wasted re-render doesn't mean it's the cause of a performance bottleneck.

oliviertassinari commented 5 years ago

@pytyl I have looked at your codesandbox, there is a problem with the rendering architecture. You rerender everything when the menu item is clicked. Your GlobalContext jumps the pure logic.

@eps1lon I think that we should close this issue. It would be better to focus on specifically identified issues.

eps1lon commented 5 years ago

TL;DR: Create context slices, memoize context values, no issue with material-ui specifically: https://codesandbox.io/s/8lx6vk2978

Did some digging and the issue is that you have this big global context that is re-created during render. You re-render your App when you click at which point the global context is re-created. Your CategoryItem is listening to it which appears 100 times in your App. Since you have 100 material-ui MenuItems you encounter the classic death by a thousand cuts.

So ironically part of the solution is memoizing a context value but the important part is identifyng separate context slices. It seems like a state and dispatch context is appropriate. This is recommended when using useContext with useReducer and seems to fit here to.

This can create quite a big tree and render props hell the more contexts you have. I encourage you to have a look at useContext. It will help alot if you start facing these issues.

@oliviertassinari It's good issue to collect common pitfalls with solutions. We can decide if we want to create separate issues from it.

pytyl commented 5 years ago

@oliviertassinari @eps1lon thanks for revising! Performance seems great.

mankittens commented 4 years ago

I just had a problem with slow rendering performance. I solved it entirely by replacing all instances of the <Box> component with <div>s. I debugged using the react devtools flamegraph, and I went from around 420ms to 20ms.

With <Box>es;

Screen Shot 2019-08-16 at 12 47 25 AM

Without <Box>es:

Screen Shot 2019-08-16 at 12 42 38 AM
oliviertassinari commented 4 years ago

@mankittens You could keep the Box component, using styled-components as the style engine. The performance would be much better. It should get better with JSS in the near future https://github.com/mui-org/material-ui/pull/16858.

I'm closing this issue. We need a dedicated performance report for each potential area of improvement, not an umbrella thread.

CrytoCodeWizard commented 6 months ago

First of all Thanks for the community. I have some difficulties now by using minimal Material UI theme. This is a simple explanation what I am having.

I am using Minimal Material UI theme to build my user interface now. I am using FormProvider component to add user information. But I have issue in this, when I submit my data, there is not submit data. How can I fix this? Please help me and give me solution.

This is my UserModal component : import * as Yup from 'yup' import PropTypes from 'prop-types'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import React, { useState, useEffect, forwardRef, useCallback } from "react";

import Slide from '@mui/material/Slide'; import Dialog from '@mui/material/Dialog'; import Button from '@mui/material/Button'; import MenuItem from '@mui/material/MenuItem'; import Grid from '@mui/material/Unstable_Grid2'; import LoadingButton from '@mui/lab/LoadingButton'; import DialogTitle from '@mui/material/DialogTitle'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent';

import axios, { endpoints } from 'src/utils/axios';

import FormProvider, { RHFSelect, RHFTextField } from 'src/components/hook-form';

const Transition = forwardRef((props, ref) => <Slide direction="up" ref={ref} {...props} />);

const UserModal = ({ dialog, type, user }) => { const [customerName, setCustomerName] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [webhook, setWebhook] = useState(""); const [casinoWallet, setCasinoWallet] = useState(""); const [withdrawWalletPrivateKey, setWithdrawWalletPrivateKey] = useState(""); const [withdrawWalletAddress, setWithdrawWalletAddress] = useState(""); const [agencyWalletAddress, setAgencyWalletAddress] = useState(""); const [depositPercentageFee, setDepositPercentageFee] = useState(0); const [withdrawPercentageFee, setWithdrawPercentageFee] = useState(0); const [fixFee, setFixFee] = useState(0); const [userRole, setUserRole] = useState("USER");

useEffect(() => {
    if (type === "Create") {
        setCustomerName("");
        setEmail("");
        setWebhook("");
        setUserRole("USER");
    } else if (type === "Update") {
        setCustomerName(user.customerName);
        setEmail(user.email);
        setWebhook(user.webhook);
        setUserRole(user.role);
    }
}, [user, type]);

const UserSchema = Yup.object().shape({
    // customerName: Yup.string().required('Customer Name is required'),
    // email: Yup.string().required('Email is required').email('Email must be a valid email address'),
    // webhook: Yup.string().required('Web Hook is required'),
    // password: Yup.string().required('Password is required'),
});

const userDefaultValues = {
    customerName,
    email,
    password,
    webhook,
    casinoWallet,
    withdrawWalletAddress,
    withdrawWalletPrivateKey,
    agencyWalletAddress,
    depositPercentageFee,
    withdrawPercentageFee,
    fixFee,
    userRole,
}

const methods = useForm({
    resolver: yupResolver(UserSchema),
    userDefaultValues,
});

const {
    reset,
    handleSubmit,
    formState: { isSubmitting },
} = methods;

const onSubmit = handleSubmit((data) => {
    console.log("submit data : ", data);
    if (type === "Created") {
        axios
            .post(endpoints.user.list, data)
            .then(response => {
                console.log(response.data);
                dialog.onFalse();
            })
            .catch(error => {
                console.log("create user error : ", error);
                reset();
            });
    } else if (type === "craere") {
        axios
            .put(`${endpoints.user.list}/${user.id}`, data, {
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then(response => {
                console.log(response.data);
                dialog.onFalse();
            })
            .catch(error => {
                console.log("create user error : ", error);
                reset();
            });
    }
});

const handleUserChange = useCallback((event) => {
    const { name, value } = event.target;

    const stateUpdateFunctions = {
        customerName: setCustomerName,
        email: setEmail,
        password: setPassword,
        webhook: setWebhook,
        casinoWallet: setCasinoWallet,
        withdrawWalletAddress: setWithdrawWalletAddress,
        withdrawWalletPrivateKey: setWithdrawWalletPrivateKey,
        agencyWalletAddress: setAgencyWalletAddress,
        depositPercentageFee: setDepositPercentageFee,
        withdrawPercentageFee: setWithdrawPercentageFee,
        fixFee: setFixFee,
        role: setUserRole
    };

    const setState = stateUpdateFunctions[name];
    if (setState) {
        setState(value);
    }
}, []);

return (
    <Dialog
        TransitionComponent={Transition}
        open={dialog.value}
        onClose={dialog.onFalse}>
        <DialogTitle>
            {type === "Create" ? "Create" : "Edit"} User
        </DialogTitle>
        <FormProvider methods={methods} onSubmit={onSubmit}>
            <DialogContent>
                <Grid container spacing={1}>
                    <Grid xs={12} md={6}>
                        <RHFTextField
                            autoFocus
                            fullWidth
                            type="text"
                            name='customerName'
                            margin="dense"
                            variant="outlined"
                            label="Customer Name"
                            onChange={(event) => handleUserChange(event)}
                            value={customerName}
                        />
                    </Grid>
                    <Grid xs={12} md={6}>
                        <RHFTextField
                            fullWidth
                            type="email"
                            name='email'
                            margin="dense"
                            variant="outlined"
                            label="Email Address"
                            onChange={(event) => handleUserChange(event)}
                            value={email}
                        />
                    </Grid>
                    <Grid xs={12} md={6}>
                        <RHFTextField
                            fullWidth
                            type="password"
                            name='password'
                            margin="dense"
                            variant="outlined"
                            label="Password"
                            onChange={(event) => handleUserChange(event)}
                            value={password}
                        />
                    </Grid>
                    <Grid xs={12} md={6}>
                        <RHFTextField
                            fullWidth
                            type="text"
                            name='webhook'
                            margin="dense"
                            variant="outlined"
                            label="Web Hook"
                            onChange={(event) => handleUserChange(event)}
                            value={webhook}
                        />
                    </Grid>
                    <Grid xs={12} md={6}>
                        <RHFTextField
                            fullWidth
                            type="text"
                            name='casinoWallet'
                            margin="dense"
                            variant="outlined"
                            label="Casino Wallet"
                            value={casinoWallet}
                            onChange={(event) => handleUserChange(event)}
                        />
                    </Grid>
                    <Grid xs={12} md={6}>
                        <RHFTextField
                            fullWidth
                            type="text"
                            name='withdrawWalletAddress'
                            margin="dense"
                            variant="outlined"
                            label="Withdraw Wallet Address"
                            value={withdrawWalletAddress}
                            onChange={(event) => handleUserChange(event)}
                        />
                    </Grid>
                    <Grid xs={12} md={6}>
                        <RHFTextField
                            fullWidth
                            type="text"
                            name='withdrawWalletPrivateKey'
                            margin="dense"
                            variant="outlined"
                            label="Withdraw Wallet PrivateKey"
                            value={withdrawWalletPrivateKey}
                            onChange={(event) => handleUserChange(event)}
                        />
                    </Grid>
                    <Grid xs={12} md={6}>
                        <RHFTextField
                            fullWidth
                            type="text"
                            name='agencyWalletAddress'
                            margin="dense"
                            variant="outlined"
                            label="Agency Wallet Address"
                            value={agencyWalletAddress}
                            onChange={(event) => handleUserChange(event)}
                        />
                    </Grid>
                    <Grid xs={12} md={4}>
                        <RHFTextField
                            fullWidth
                            type="number"
                            name='depositPercentageFee'
                            margin="dense"
                            variant="outlined"
                            label="Deposit Percentage Fee"
                            value={depositPercentageFee}
                            onChange={(event) => handleUserChange(event)}
                        />
                    </Grid>
                    <Grid xs={12} md={4}>
                        <RHFTextField
                            fullWidth
                            type="number"
                            name='withdrawPercentageFee'
                            margin="dense"
                            variant="outlined"
                            label="Withdraw Percentage Fee"
                            value={withdrawPercentageFee}
                            onChange={(event) => handleUserChange(event)}
                        />
                    </Grid>
                    <Grid xs={12} md={4}>
                        <RHFTextField
                            fullWidth
                            type="number"
                            name='fixFee'
                            margin="dense"
                            variant="outlined"
                            label="Fix Fee"
                            value={fixFee}
                            onChange={(event) => handleUserChange(event)}
                        />
                    </Grid>
                    <Grid xs={12} md={12}>
                        <RHFSelect
                            variant="outlined"
                            label="User Role"
                            name='role'
                            value={userRole}
                            onChange={(event) => handleUserChange(event)}
                        >
                            {[
                                { value: "USER", label: "User" },
                                { value: "ADMINISTRATOR", label: "Administrator" }
                            ].map((option) => (
                                <MenuItem key={option.value} value={option.value}>
                                    {option.label}
                                </MenuItem>
                            ))}
                        </RHFSelect>
                    </Grid>
                </Grid>
            </DialogContent>
            <DialogActions>
                <Button onClick={dialog.onFalse} variant="outlined" color="warning">
                    Cancel
                </Button>
                <LoadingButton
                    color="success"
                    type="submit"
                    variant="contained"
                    loading={isSubmitting}
                >
                    {type === "Create" ? "Create" : "Update"} User
                </LoadingButton>
            </DialogActions>
        </FormProvider>
    </Dialog>
);

}

UserModal.propTypes = { dialog: PropTypes.object.isRequired, type: PropTypes.string.isRequired, user: PropTypes.object.isRequired, }

export default UserModal;