0

The React parent component rendering process automatically compresses rendering of child components. It can be a performance boost for most of application, but situation like this can be a serious problem.

I have 2 components :

  1. File list sidebar component
  2. Text editor component (I'm using Slate for now)

Two component is child component of Home component.

When user clicks the sidebar item, it should :

  1. Highlight the selected item
  2. Set a new editor state by reading the text file

Second step can be a bottleneck. The problem here is, that the React stupidly compresses the seperated two operations altogether. This causes the user can't see item highlight right after click! User Must wait for reading the file, and set a new editor state, and then, finally, item can get highlighted.

This delay, is causing badly at user experience. Did you ever used a text editor that reacts your click about ~500ms after? The program looks "slower".

componentDidUpdate(prevProps) {
    console.log('componentDidUpdate!');
    const { currentDir, actions, currentFile } = this.props;
    if (prevProps.currentDir !== currentDir) {
      updatedFileList(currentDir);
    } else if (prevProps.currentFile !== currentFile) {
      updateEditorContent(currentFile);
    }
  }

This is the componentDidUpdate. This updates editor state.

redux-logger.js:1  action SELECT_FILE_LIST_ITEM @ 21:29:18.332
21:29:18.338 redux-logger.js:1  action UPDATE_CURRENT_FILE @ 21:29:18.338
21:29:18.347 Home.js:287 render!!
21:29:18.356 Home.js:44 componentDidUpdate!
21:29:18.366 redux-logger.js:1  action UPDATE_EDITOR_CONTENT @ 21:29:18.357
21:29:18.373 Home.js:287 render!!
21:29:18.678 Home.js:44 componentDidUpdate!

The log says it componentDidUpdated at 21:29:18.356, but actual HTML DOM is not updated yet. It acutally updates DOM after 21:29:18.678 Home.js:44 componentDidUpdate!.'

How to seperate two componentDidUpdates, to update DOM seperately?

Hoseong Son
  • 247
  • 4
  • 18

1 Answers1

0

I think you should be able to solve this by triggering the second update asynchronously:

componentDidUpdate(prevProps) {
    console.log('componentDidUpdate!');
    const { currentDir, actions, currentFile } = this.props;
    if (prevProps.currentDir !== currentDir) {
      updatedFileList(currentDir);
    } else if (prevProps.currentFile !== currentFile) {
      setTimeout(()=>{updateEditorContent(currentFile);}, 0);
    }
  }

I didn't actually try this out, so I apologize if there are any syntax issues.

In general, what React is doing is desirable -- not wasting time updating the DOM two times when one state update immediately triggers another state update. This avoids DOM flicker when both updates are quick.

React is able to do this because it splits its work into two phases

  • a rendering/reconciliation phase in which React figures out what should be changed with the next update to the DOM
  • a commit phase where those updates are actually applied to the DOM

When the rendering triggers another rendering, React will hold off on the commit phase and batch up the DOM updates for one commit. The setTimeout above will cause that state update to wait until after the commit phase for the first update.

I think future React features around asynchronous mode will provide nicer ways of dealing with this kind of scenario where one update is high priority and the other is lower priority.

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
  • You're right. That solves the problem. But, I want to know the under the hood. I'm React noob, so could you please explain the how asynchronizing the state update can make a seperate React DOM update? – Hoseong Son Jan 01 '19 at 12:59
  • 1
    I've updated my answer to explain this further. You can find more detail in some links about the React Fiber Reconciler at the end of my answer here: https://stackoverflow.com/questions/53974865/how-do-react-hooks-determine-the-component-that-they-are-for/53980190#53980190 – Ryan Cogswell Jan 01 '19 at 13:12