Nested-loop React components with Flux, change-listeners on parent or children?
Asked Answered
C

1

8

I'm building a Word Dojo clone in React/Flux. The game is essentially Boggle - you make words by clicking on adjacent letters in a grid:

enter image description here

My React components with their source:

  1. Gameboard
  2. TileColumn
  3. Tile

All of the source code can be viewed here.


How it's working right now:

There's a GameStore that holds a two-dimensional array of javascript objects. the objects have a 'letter' string value and an 'active' boolean value. When the user clicks a letter, that dispatches to the GameStore, which updates that two-dimentional array and emits a Change event.

The GameBoard component listens for that change event, and then re-renders 10 TileColumns, which in turn render 10 Tiles each. GameBoard has the store's data as part of its state, and tiles have their own letter/active status as props.

The problem is that changing 1 letter causes all 100 tiles to be re-rendered.


shouldComponentUpdate

I tried using shouldComponentUpdate on the Tile to specify that it should only update if its 'active' value has changed, but the problem is that both this.props.active and nextProps.active are always the same value: either they're both false, or both true.


Deferring responsibility to the children

The other idea I had was to have each Tile responsible for its own updating, by registering change listeners on the tiles directly. I got a warning that I was exceeding the number of listeners, and it does seem like 100 change listeners all firing on every letter update would be less efficient. Although that's all just Javascript, so we'd be avoiding some DOM manipulation...


Performance

I fired up the Profiler and right now, with the parent doing all of the state management, it's taking 40ms to re-render the whole board on letter click. Which isn't bad, but when the game gets more complex, I'm worried it'll become a noticeable delay.


Help needed

Specifically I'm looking for advice on best practices in this situation (when you have nested, iterated components), and if shouldComponentUpdate is the solution but I'm just using it wrong.

Thanks!

Counterforce answered 20/3, 2015 at 12:21 Comment(0)
O
12

Yep, this is the classic example for why React isn't fast by default. I have a pretty long example here for exactly what kind of problem you're trying to solve.

Basically you have two typical problems:

  1. The way you initialize the tiles on the board is fine, but the way you modify the values for the tile just mutates the object. This makes it hard to know if a certain object has changed.

  2. React naively recomputes the entire application by default. You can only prevent expensive recomputation of elements with render() if you use shouldComponentUpdate intelligently.

Solution:

Use shouldComponentUpdate (or just use the ReactComponentWithPureRenderMixin) to prevent wasteful recomputation in Tile. Of course, this won't work unless you do a couple of things.

Solutions being:

  1. You know which properties are allowed to be mutated and set up shouldComponentUpdate based on those.

Is only tile.active allowed to be changed? You might be able to just define your callback so that you only check for the equality of prevProps.tile.active === this.props.tile.active.

  1. Create a new object so that shallow comparison of object references is easy.

You probably already know that var a = {}; var b = a will make it so that a === b, and that var c = {}; var d = {}; will make it so that c !== d. Replace the tile object entirely whenever you do updates so that you can use the new tile object where the old one was. This way, fast performance is literally only mixins: [ReactComponentWithPureRenderMixin] away.

This might be way more crap than you wanted, but it's pretty much how I get any kind of collection to render well in React. Without these techniques, I literally can't get my crappy etch-a-sketch component to work without grinding to a halt.

Good luck!

Osrock answered 20/3, 2015 at 13:25 Comment(2)
You should put a check in TileColumn's shouldComponentUpdate as well. And ImmutableJS makes all this easier for PureRenderMixin to work with. Switch to ImmutableData, use the mixin in all your components, and you're done.Descent
Thanks! This was super helpful. I'm pretty sure the reason shouldComponentUpdate wasn't working was because of the javascript variables-just-point-to-objects thing; using Immutable.js should fix this and it's something I've meant to do anyway.Counterforce

© 2022 - 2024 — McMap. All rights reserved.