28

I adapted the following component definition from here as shown below. However, unlike the example my component re-renders every time I move the mouse on it.

The re-rendering is very noticable:

enter image description here

Does anyone have an idea why this is happening ?

import React, { Component } from 'react';
import { PieChart, Pie, Sector } from 'recharts';
import { Segment, Header, Dimmer, Loader, Grid } from 'semantic-ui-react';

const renderActiveShape = (props) => {

const RADIAN = Math.PI / 180;
const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle,
    fill, payload, percent, value } = props;
const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my;
const textAnchor = cos >= 0 ? 'start' : 'end';

return (
    <g>
        <text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>{payload.name}</text>
        <Sector
            cx={cx}
            cy={cy}
            innerRadius={innerRadius}
            outerRadius={outerRadius}
            startAngle={startAngle}
            endAngle={endAngle}
            fill={fill}
            />
        <Sector
            cx={cx}
            cy={cy}
            startAngle={startAngle}
            endAngle={endAngle}
            innerRadius={outerRadius + 6}
            outerRadius={outerRadius + 10}
            fill={fill}
            />
        <path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
        <circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
        <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill="#333">{`PV ${value}`}</text>
        <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={18} textAnchor={textAnchor} fill="#999">
            {`(Rate ${(percent * 100).toFixed(2)}%)`}
        </text>
    </g>
);
};

export default class TwoLevelPie extends Component {

constructor(props) {
    super(props);
    this.state = { activeIndex: 0 }
    this.onPieEnter = this.onPieEnter.bind(this);
}

onPieEnter(data, index) {
    this.setState({
        activeIndex: index
    });
}

render() {

    const data = [{ name: 'Group A', value: 400 }, { name: 'Group B', value: 300 },
    { name: 'Group C', value: 300 }, { name: 'Group D', value: 200 }];

    return (
        <Segment inverted>
            <PieChart width={800} height={400} onMouseEnter={this.onPieEnter}>
                <Pie
                    activeIndex={this.state.activeIndex}
                    activeShape={renderActiveShape}
                    data={data}
                    cx={300}
                    cy={200}
                    innerRadius={60}
                    outerRadius={80}
                    fill="#8884d8" />
            </PieChart>
        </Segment>
    );
}
}
Cemre Mengü
  • 18,062
  • 27
  • 111
  • 169
  • It may help you get good answers if you make your code [runnable on-site](http://meta.stackoverflow.com/questions/338537/how-do-i-create-a-reactjs-stack-snippet-with-jsx-support). – T.J. Crowder Jan 20 '17 at 11:52
  • Didn't know that thanks I will try now. – Cemre Mengü Jan 20 '17 at 11:53
  • Maybe it has to do with the fact that you change the state every time you do a mouseover (`onPieEnter`)? – Dimitris Karagiannis Jan 20 '17 at 11:54
  • Yes, but how is it working for the example ? @DimitrisKaragiannis – Cemre Mengü Jan 20 '17 at 11:56
  • @Cemre I cannot see the react structure of it in my dev tools. Are you sure it does not re-render even when you hover over the same sector? You would not notice it even if it did. – Dimitris Karagiannis Jan 20 '17 at 11:58
  • @DimitrisKaragiannis I edited the post showing the re-render in action. It renders in every kind of mouse interaction – Cemre Mengü Jan 20 '17 at 12:06
  • @Cemre Well, I am not sure friend. It seems as if yours does a full re-render, while the example only updates the active section. Have you, by any chance added any transitions to your pie chart? That could also explain the visual effect. – Dimitris Karagiannis Jan 20 '17 at 12:24

1 Answers1

42

Pure components defined as function will always re-render.

Convert the component to a class and prevent the re-render in shouldComponentUpdate() returning false.

The signature is shouldComponentUpdate(nextProps, nextState). Say, you prevent re-render by verifying that componet's params haven't changed:

shouldComponentUpdate(nextProps, nextState){
   return !equals(nextProps, this.props); // equals() is your implementation
}
Amio.io
  • 20,677
  • 15
  • 82
  • 117
  • 4
    "Pure components defined as function will always re-render" this is correct. The moment new props get passed down it re-renders. – Dimitris Karagiannis Jan 20 '17 at 11:56
  • Do you mind giving some hints on how to do the rerender in `shouldComponetUpdate` ? I am bit new to react – Cemre Mengü Jan 20 '17 at 11:57
  • @DimitrisKaragiannis I've modified the answer. Still I'd like to post the link from the doc. – Amio.io Jan 20 '17 at 11:58
  • So, I moved it to another class and calling it as `activeShape={}` and using `shouldComponentUpdate` as you suggested but now it does not update at all. The label around the pie should still be moving. – Cemre Mengü Jan 20 '17 at 12:10
  • I've opened the fiddle and see what you want to achieve. Please how you use your component. – Amio.io Jan 20 '17 at 12:12
  • Also, just to clarify, 're-rendering' does not mean that the actual DOM gets re-rendered every time a `setState` happens. The virtual DOM is the one that is *always* re-rendered when a `setState` happens. If the previous and current state of the virtual DOM differ, that's when the actual DOM gets re-rendered/updated as well. See [this](http://stackoverflow.com/questions/24718709/reactjs-does-render-get-called-any-time-setstate-is-called) question and answers – Dimitris Karagiannis Jan 20 '17 at 12:21
  • 12
    Where does this `equals` come from? – JacobIRR May 31 '18 at 16:59
  • 3
    @JacobIRR I'd also like to know. – blankface Jun 06 '18 at 06:26
  • 3
    @ZeroDarkThirty @JacobIRR `equals()` is a method that you have to implement. It should define your "version" of equality. – Amio.io Jun 06 '18 at 09:07
  • Thank you. This was clearly explained and really helped me. – Andy Hoffman Sep 10 '18 at 10:00
  • 4
    regarding "Pure components defined as function will always re-render" -> in React 16.6 you can wrap those in React.memo() to avoid that (unless obviously the properties changed) – Leon Nov 29 '18 at 23:52
  • 1
    `!equals()` could be `!Object.is(nextProps, this.props)` – Abk Aug 06 '19 at 11:33
  • 1
    Here I am in 2020 using hooks and this aswer do worked and saved my day! – Marlon Paranhos May 23 '20 at 13:47
  • Thanks, this saves a ton!! I had a similar issue and it was actually caused by incorrect use of the router. Previously, the component was passed into as "component" prop, and the value was a anonymous arrow function. After changing component to the children of the fixes the problem. – Charles Zhao Jan 06 '21 at 08:00