2

JSFiddle

var gridWidth = 15;
var gridHeight = 15;

var grid = [];
for(var y=0; y<gridHeight; y++) {
    grid.push([]);
    for(var x=0; x<gridWidth; x++) {
        grid[y][x] = {c:0};
    }
}

var reducer = function(state = grid, action) {
  let newState = clone(state);
  if(action.type==='CC') {
    newState[action.payload.y][action.payload.x].c = action.payload.c;
  }
  return newState;
}
var store = Redux.createStore(reducer);

var colours = ['black', 'grey'];
var Provider = ReactRedux.Provider;
var connect = ReactRedux.connect;
var map = function(state) {
    return {grid:state};
}

var Box = React.createClass({
    width: 15,
    handleClick: function(x, y) {
        this.props.dispatch({type:'CC', payload: {c:1, x:x, y:y}});
    },
    render: function() {
        console.log('boxrender')
        var pos = {
          top: this.props.y * this.width,
          left: this.props.x * this.width,
          width: this.width,
          height: this.width,
          background: this.props.colours[this.props.box.c]
        }
        return <div className="box" style={pos} onMouseOver={() => this.handleClick(this.props.x, this.props.y)}></div>
    }
});
var ConnectedBox = connect(map)(Box);

var Grid = React.createClass({

  render: function() {
    console.log('grid render')
    return (
      <div>
        {this.props.grid.map((row, y) => {
          return row.map((box, x) => {
            return <ConnectedBox key={'x'+x+'y'+y} box={box} x={x} y={y} colours={this.props.colours} />;
          })
        })}
      </div>
    )
  }
});
var ConnectedGrid = connect(map)(Grid);

ReactDOM.render(
    <Provider store={store}>
      <ConnectedGrid colours={colours} />
    </Provider>,
    document.getElementById('container')
);

I have a large grid that I want to be able to 'colour in' on mouseover, using redux to make the changes, however even though only one box object is being changed, react is re-rendering every box each time one changes and I don't know why? It's making it super slow

Abby
  • 3,169
  • 1
  • 21
  • 40

3 Answers3

1

Optimized: https://jsfiddle.net/p6z5veo6/5/


Basic perf rules for React

  • Use React.PureComponent or React.Component with custom shouldComponentUpdate
  • Pass only needed slice of the store to your components
  • Avoid re-rendering if props or state has not changed
  • Avoid creating new instances of objects and binding functions on each render (very often happens when () => {} is pass to onClick etc.)

What I did and why

  • Implemented custom shouldComponentUpdate for Grid and Box components

    class Box extends React.Component {
      shouldComponentUpdate(nextProps) {
        return nextProps.colour !== this.props.colour;
      }
    
    // ...
    
    class Grid extends React.Component {
      shouldComponentUpdate(nextProps) {
        return false;
      }
    
  • In this case Grid component doesn't have to be re-rendered every time when store is updated because we want to re-render only specific Box component so we use Grid only for initial rendering and pass only relevant data from the store to Box

    const ConnectedBox = connect((store, { x, y }) => ({
      colour: store[y][x].c
    }))(Box);
    
  • Implemented simpler version of your reducer, just update main reference and object which contain new colour

    var reducer = (state = grid, action) => {
      if (action.type === "CC") {
        const { x, y, c } = action.payload;
    
        const newState = [...state];
        newState[y][x] = { c };
    
        return newState;
      }
    
      return state;
    };
    
  • Bind handleClick in costructor

    class Box extends React.Component {
      constructor(props) {
        super(props);
    
        this.width = 15;
        this.handleClick = this.handleClick.bind(this);
      }
    

Very similar project was implemented and described in the article: An artificial example where MobX really shines and Redux is not really suited for it.

Repository of the project from article is also available on github. You can read article, check repo and this pull request so you can learn how eventually could you more improve your code because the problem is with the data structure (array) that you decided to use to store in redux.

Dawid Karabin
  • 5,113
  • 1
  • 23
  • 31
  • Thanks for your thorough answer, this is great and has given me loads to think about and new approaches to try. Your fiddle is much faster and I can see it rendering only the square; it still inevitably slows down if i bump the grid up to something more like 150x250, but I'll keep trying! – Abby Jul 27 '17 at 07:55
  • Check also this answer https://stackoverflow.com/questions/34782249/can-a-react-redux-app-really-scale-as-well-as-say-backbone-even-with-reselect/34788570#34788570 which suggests two interesting solutions: custom `connect` HOC and using different data structure. – Dawid Karabin Jul 27 '17 at 09:17
0

You're rendering a list of lists, try wrapping each inner list in a div so it can get a proper key:

{
  this.props.grid.map((row, y) => 
    <div key={y}>
      {
        row.map((box, x) => 
          <ConnectedBox key={'x'+x+'y'+y} box={box} x={x} y={y} colours={this.props.colours} />)
      }
    </div>)
}
Danny Delott
  • 6,756
  • 3
  • 33
  • 57
0

I don't see what's causing it at a glance, but if you just need a quick fix you can add a shouldComponentUpdate to prevent it from rendering unless the box.c changes:

var Box = React.createClass({
  shouldComponentUpdate(nextProps) {
    return this.props.box.c !== nextProps.box.c;
  },

Working fiddle: https://jsfiddle.net/5c21yw3j/

Austin Greco
  • 32,997
  • 6
  • 55
  • 59