reactjsmaterial-uimui-datatablenivo-react

Nivo charts wont animate if its inside a styled Material UI component


I am using @nivo/core and @mui/material.

After a lot of debugging, I was able to narrow down my problem: that our Nivo chart seems to be flickering and won't animate properly, if it is inside a styled() Material UI component.

What can be the core problem here? Did I find a bug in Mui or I am using it badly somehow? What is the underlying issue? I did not have any luck finding out more.

The following code shows the problem:

Stackblitz example here

import { Paper, styled, Table, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
import { ResponsiveRadialBar } from '@nivo/radial-bar';
import * as React from 'react';
import './style.css';

export default function App() {
  const [testData, setTestData] = React.useState<any>(50);

  function generateNewdata() {
    setTestData(Math.floor(Math.random() * 100));
  }

const StyledTable = styled(Table)(({ theme }) => ({
    background:"gray",
  }));

  return (
    <div>
      <section>
        <p>Click on the button to begin testing the animation.</p>
        <button onClick={() => generateNewdata()}>Generate</button>
        <MyGauge label="Performance" value={testData} color="#00b0f0"></MyGauge>
      </section>
      <section>
        <p>The table below me is in a styled container. it wont animate, and is flickering :( </p>
        <TableContainer>
        <StyledTable  component={Paper}>
          <TableHead>
            <TableRow>
              <TableCell>
                <MyGauge
                  label="Performance"
                  value={testData}
                  color="#00b0f0"
                ></MyGauge>
              </TableCell>
            </TableRow>
          </TableHead>
          </StyledTable>
        </TableContainer>
      </section>
      <section>
        <p>The table below me is in a styled with sx. it  animates properly. </p>
        <TableContainer>
        <Table style={{background: 'darkgreen'}}  component={Paper}>
          <TableHead>
            <TableRow>
              <TableCell>
                <MyGauge
                  label="Performance"
                  value={testData}
                  color="#00b0f0"
                ></MyGauge>
              </TableCell>
            </TableRow>
          </TableHead>
          </Table>
        </TableContainer>
      </section>
    </div>
  );
}

export interface MyGaugeProps {
  label: string;
  value: number;
  color: string;
}

export const MyGauge = (props: MyGaugeProps) => {
  // You may use any hook-based solution as long as you:
  // - make sure the data is immutable
  // - make sure that you only update once for animation. (https://reactjs.org/docs/hooks-rules.html)
  // anything differently will make the animation stop and just "flicker" or not happen at all.
  const chartData = React.useMemo(() => {
    return [
      {
        id: props.label,
        data: [
          {
            x: props.label,
            y: props.value,
          },
        ],
      },
    ];
  }, [props]);

  return (
    <div style={{ height: 200, width: 200, position: 'relative' }}>
      <ResponsiveRadialBar
        data={chartData}
        maxValue={100}
        padding={0.4}
        innerRadius={0.25}
        startAngle={0}
        endAngle={3660}
        enableTracks={false}
        enableRadialGrid={false}
        enableCircularGrid={false}
        radialAxisStart={null}
        circularAxisOuter={null}
        colors={[props.color]}
        cornerRadius={1}
        animate={true}
        // if you delete this, we will still fallback to the default animation.
        motionConfig={{
          mass: 1,
          tension: 170,
          friction: 200,
          clamp: false,
          precision: 0.01,
          velocity: 0,
        }}
        transitionMode={'startAngle'}
      />

      <div
        style={{
          position: 'absolute',
          width: '100%',
          top: 0,
          bottom: 0,
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'center',
          fontSize: 10,
          textAlign: 'center',
          pointerEvents: 'none',
        }}
      >
        {props.value}%
      </div>
    </div>
  );
};


Solution

  • I got an answer here:

    "hey @ForestG the problem is you have declared StyledTable inside App function, so every time state changes inside App new StyledTable function is created. Since new StyledTable is created everytime, react unmounts and remounts children inside StyledTable, because of this unmount->remount->unmount behavoiur you re seening flickering behaviour.

    To fix this just create StyledTable outside App.

    import './style.css';
    
    const StyledTable = styled(Table)(({ theme }) => ({
      background: 'gray',
    }));
    
    export default function App() {
      const [testData, setTestData] = React.useState<any>(50);
    
      function generateNewdata() {
        setTestData(Math.floor(Math.random() * 100));
      }
    

    working example: https://stackblitz.com/edit/react-ts-jrzrc2?file=App.tsx"