1

Problem

*That is not exactly my case but here is what matters.
Imagine an app that renders a list of elements:

const ElementsList = ({elements, <onElementTextChange>}) => (
  <div className='ElementsList'>
    {elements.map(el => (
      <Element
        key={el.id}
        element={el}
      />
    ))}
  </div>
)

Elements are kept in state of the parent component that renders ElementsList. Each element is a simple object:

this.state = {elements: [
  {id: 1, text: 'apple'},
  {id: 23, text: 'banana'},
]}
...
render() { ... <ElementsList elements={this.state.elements}/> ... }

Each element renders with the input field nearby to change its text:

export const Element = ({element, <onTextChange>}) => (
  <div className='Element'>
    <div className='element-text'>{element.text}</div>
    <input type='text' value={element.text} onChange={???}></input>
  </div>
)

Consider that there can be a lot of elements (the more the app can handle the merrier).
Now the question is how should we go about updating texts through those inputs.

Solutions

I see 2 options (some combinations of them are possible):

1. Proper react way (is it?) - notify parent component about the change

  • at Element: onChange={evt => onTextChange(evt.target.value)}
  • at ElementsList: onTextChange={newText => onElementTextChange(el, newText)}
  • at parent component: onElementTextChange={ (element, newText) => {make new elements array with the updated element somehow; setState(elements)} }

That solution seems too awkward to implement. Also it is slow compared to the alternatives below. Events happening on input change (correct me if I'm wrong):

  1. notify parent component about the change through the chain of components
  2. parent component constructs new elements array and calls setState
  3. the whole virtual DOM gets updated (including each element)
  4. updated virtual DOM is compared to the previous virtual DOM version
  5. one Element is updated in the real DOM

2. Update props of the Element directly and forceUpdate()

  • rewrite Element to be a class instead of functional component
  • at Element: onChange={evt => {element.text = evt.target.value; this.forceUpdate()}}

Events happening on input change:

  1. props.element.text is changed and forceUpdate is called
  2. only one Element in the virtual DOM is updated
  3. updated subtree of the virtual DOM (one Element) is compared to the old virtual DOM
  4. one Element is updated in the real DOM

Questions

Currently I'm using an intermediate approach but more on the "proper react way" side. And it isn't fast enough.
I like the second option more, it requires less code and seems like it should work much faster (in theory).

  1. Will I get significantly better perfomance by using the second approach? (Remember that there are thouthands of elements)
  2. What specific problems this approach can lead to? Yeah, yeah, I know the code is less transparent and this not how it is meant to be.
  3. Do you see any other way to get the perfomance up? I'd be happy to try even less react-like suggestions, might consider giving up on react partially or entirely.
grabantot
  • 2,111
  • 20
  • 31
  • 1
    I would use option 2, but I'm not sure how to answer your questions. To measure performance, React just introduced a [profiler](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html) so you can try both and see what the performance impact is. – Toby Oct 05 '18 at 18:38
  • @Toby Thanks. I will try try measuring perfomance – grabantot Oct 06 '18 at 05:03

2 Answers2

1

Immutable data structures address your performance concerns through techniques like memoization and structural sharing.

You should look into using Immutable.js. Then you can use React.PureComponent to do efficient value equality checks on large data structures.

One downside is that Immutable.js data structures do not mix well with regular mutable javascript data structures, which can be frustrating. Some languages like Elm or ClojureScript offer first class support for these kinds of data structures, making it a much more consistent experience.

dpren
  • 1,225
  • 12
  • 18
  • It doesn't seem to help at all. React will still rerender all elements to the virtual DOM and perform trees comparison – grabantot Oct 06 '18 at 04:58
1

you can do like this

 this.state = {elements: [
  {id: 1, text: 'apple'},
   {id: 23, text: 'banana'},
   ]}

   // creating a update function which you will pass down to the child

    updateState = (id, val) => {
           const newElements = this.state.elements;
            newElements.map(item => {
               if(item.id === id) item.text = val;
             });
          this.setState({
           elements: newElements,
          })
       }
 render() { ... <ElementsList elements={this.state.elements} updateState ={this.updateState}/> ... }

now in the ElementsList

       const ElementsList = ({elements, updateState,  <onElementTextChange>}) => (
       <div className='ElementsList'>
        {elements.map(el => (
       <Element
        key={el.id}
        element={el}
        updateState = {updateState}
         />
        ))}
        </div>
         )

now in the element

    export class Element extends React.Component{

     state = {
       value: '',  
       }
         onChangeValue = (e) => { 
              this.setState({ value: e.target.value});
            // calling the parent function directly
               this.props.updateState(this.props.element.id, e.target.value);
            }
       componentDidMount(){
         this.setState({
         value: this.props.element.text,
       })
         }
      render(){
        return(
              <div className='Element'>
       <div className='element-text'>{element.text}</div>
       <input type='text' value={this.state.value} onChange={this.onChangeValue}></input>
       </div>
        )
        }
       } 
  • it is a different approach. But all of the concerns about my 1st solution apply here. Except the notification chain is simpler. – grabantot Oct 06 '18 at 04:54