React: Manipulating DOM in componentDidUpdate

Created on 23 Sep 2016  ·  3Comments  ·  Source: facebook/react

Hi, this is more like a question, I'd like to open a discussion regarding DOM manipulation in componentDidUpdate.

I'm creating a React plugin. It has to do some DOM calculations, because CSS is not powerful enough to cover everything this plugin needs to do. The problem is I need to adjust some CSS properties depending on the return value of getBoundingClientRect() of some other DOM element rendered by the same component.

So technically we have 3 options:

1) Calculate getBoundingClientRect in componentDidUpdate and calling setState with calculated values. I'm afraid of the infinite loop that could happen in certain cases, so I wouldn't go for this one.

2) Do it like in 1) and play a bit with shouldComponentUpdate, but this seems very complex and not readable. A lot of questions pop up that are dependent on the certain use case.

3) Update DOM from componentDidUpdate. This is the solution I decided to go for, it works, it's simple, stable and works as intended. However I feel I'm on the edge, so I wanted to double check that this is an OK way of doing things. Is it OK to manipulate DOM in componentDidUpdate? It's called everytime render is done, and I'm OK with throwing away my changes, because for next render I'll modify DOM again.

Here's a part of the code

componentDidMount() {
  this.recalculateIndent();
}

componentDidUpdate() {
  this.recalculateIndent();
}

recalculateIndent() {
  // calculate indent based on getBoundingClientRect of some DOM reference
  this.textareaRef.style.textIndent = `${indent}px`;
}

render() {
   return (
   ...
   <textarea ref={ref => this.textareaRef = ref}/>
   ...
   );
}

Most helpful comment

@jvorcak using componentDidUpdate is the correct place to manipulate the DOM manually after a render. Per the docs:

Use this as an opportunity to operate on the DOM when the component has been updated.

Also, we try to use the issue tracker solely for feature requests and bug reports. Usage questions should be directed elsewhere, like discuss.reactjs.org or StackOverflow. Thanks!

All 3 comments

Each solution (component lifecycle method) is proper for concrete situation, but DOM manipulation is wrong. Use state and re-render component. Example for my last use case "fixed header for table + virtual scrolling for table body", I hope this will help you understand the correct flow.

  1. Component constructor: initial state value: bodyHeight = 0 (or null, as you like).
  2. First render, renders table header but empty table body (0 rows).
  3. componentDidMount - read header and container DOM height (+ some calculation...) and setState for bodyHeight (re-render).
  4. Second render, bodyHeight !== 0, table body will be rendered. Now, height parameter is known (I can set it using "style" component prop, rather than DOM manipulation), I can add components for rows.
  5. Additional event listener for "resize" (document/browser resize), add in componentDidMount, remove in componentWillUnmount. In event handler I do the same thing like in componentDidMount - read new DOM height values and setState.
  6. There is no DOM manipulation by hand, react does the job.

@jvorcak using componentDidUpdate is the correct place to manipulate the DOM manually after a render. Per the docs:

Use this as an opportunity to operate on the DOM when the component has been updated.

Also, we try to use the issue tracker solely for feature requests and bug reports. Usage questions should be directed elsewhere, like discuss.reactjs.org or StackOverflow. Thanks!

What if you want to update one Component after changing something in another?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

trusktr picture trusktr  ·  3Comments

jimfb picture jimfb  ·  3Comments

hnordt picture hnordt  ·  3Comments

UnbearableBear picture UnbearableBear  ·  3Comments

zpao picture zpao  ·  3Comments