Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using React's shouldComponentUpdate with Immutable.js cursors

I'm having trouble figuring out how to short circuit rendering a branch of a tree of React components using Immutable.js cursors.

Take the following example:

import React from 'react';
import Immutable from 'immutable';
import Cursor from 'immutable/contrib/cursor';

let data = Immutable.fromJS({
  things: [
    {title: '', key: 1},
    {title: '', key: 2}
  ]
});

class Thing extends React.Component {
  shouldComponentUpdate(nextProps) {
    return this.props.thing.deref() !== nextProps.thing.deref();
  }

  handleChangeTitle(e) {
    this.props.thing.set('title', e.target.value);
  }    

  render() {
    return <div>
      <input value={this.props.thing.get('title')} 
        onChange={this.handleChangeTitle.bind(this)} />
    </div>;
  }
}

class Container extends React.Component {
  render() {
    const cursor = Cursor.from(this.props.data, 'things', newThings => {
      data.set('things', newThings);
      renderContainer();
    });

    const things = cursor.map(thing => (
      <Thing thing={thing} key={thing.get('key')} />
    ));

    return <div>
      {things}
    </div>;
  }
}

const renderContainer = () => {
  React.render(<Container data={data} />, document.getElementById('someDiv'));
};

Say I change the first Thing's title. Only the first Thing will render with the new title and the second Thing will not re-render due to shouldComponentUpdate. However, if I change the second Thing's title, the first Thing's title will go back to '' since the second Thing's cursor is still pointing at an older version of the root data.

We update the cursors on each render of Container but the ones that don't render due to shouldComponentUpdate also don't get the new cursor with the updated root data. The only way I can see keeping the cursors up to date is to remove shouldComponentUpdate in the Thing component in this example.

Is there a way to change this example to use shouldComponentUpdate using fast referential equality checks but also keep the cursors updated?

Or, if that's not possible, could you provide an overview of how you would generally work with cursors + React components and rendering only components with updated data?

like image 874
franky Avatar asked Nov 10 '22 13:11

franky


1 Answers

I updated your code, see comments inline:

class Thing extends React.Component {
  shouldComponentUpdate(nextProps) {
    return this.props.thing.deref() !== nextProps.thing.deref();
  }

  handleChangeTitle(e) {
    // trigger method on Container to handle update
    this.props.onTitleChange(this.props.thing.get('key'), e.target.value);
  }    

  render() {
    return <div>
      <input value={this.props.thing.get('title')} 
        onChange={this.handleChangeTitle.bind(this)} />
    </div>;
  }
}

class Container extends React.Component {
  constructor() {
    super();
    this.initCursor();
  }

  initCursor() {
    // store cursor as instance variable to get access from methods
    this.cursor = Cursor.from(data, 'things', newThings => {
      data = data.set('things', newThings);
      // trigger re-render
      this.forceUpdate();
    });
  }

  render() {
    const things = this.cursor.map(thing => (
      <Thing thing={thing} key={thing.get('key')} onTitleChange={this.onTitleChange.bind(this)} />
    ));

    return <div>
      {things}
    </div>;
  }

  onTitleChange(key, title){
    // update cursor to store changed things
    this.cursor = this.cursor.update(x => {
      // update single thing
      var thing = x.get(key - 1).set('title', title);
      // return updated things
      return x.set(key - 1,thing);
    });
  }
}

const renderContainer = () => {
  React.render(<Container data={data} />, document.getElementById('someDiv'));
};
like image 200
Yevgen Safronov Avatar answered Nov 14 '22 23:11

Yevgen Safronov