[javascript] React "after render" code?

I have an app where I need to set the height of an element (lets say "app-content") dynamically. It takes the height of the "chrome" of the app and subtracts it and then sets the height of the "app-content" to fit 100% within those constraints. This is super simple with vanilla JS, jQuery, or Backbone views, but I'm struggling to figure out what the right process would be for doing this in React?

Below is an example component. I want to be able to set app-content's height to be 100% of the window minus the size of the ActionBar and BalanceBar, but how do I know when everything is rendered and where would I put the calculation stuff in this React Class?

/** @jsx React.DOM */
var List = require('../list');
var ActionBar = require('../action-bar');
var BalanceBar = require('../balance-bar');
var Sidebar = require('../sidebar');
var AppBase = React.createClass({
  render: function () {
    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar title="Title Here" />
          <BalanceBar balance={balance} />
          <div className="app-content">
            <List items={items} />
          </div>
        </div>
      </div>
    );
  }
});

module.exports = AppBase;

This question is related to javascript reactjs

The answer is


I had weird situation when i need to print react component which receives big amount of data and paint in on canvas. I've tried all mentioned approaches, non of them worked reliably for me, with requestAnimationFrame inside setTimeout i get empty canvas in 20% of the time, so i did the following:

nRequest = n => range(0,n).reduce(
(acc,val) => () => requestAnimationFrame(acc), () => requestAnimationFrame(this.save)
);

Basically i made a chain of requestAnimationFrame's, not sure is this good idea or not but this works in 100% of the cases for me so far (i'm using 30 as a value for n variable).


One drawback of using componentDidUpdate, or componentDidMount is that they are actually executed before the dom elements are done being drawn, but after they've been passed from React to the browser's DOM.

Say for example if you needed set node.scrollHeight to the rendered node.scrollTop, then React's DOM elements may not be enough. You need to wait until the elements are done being painted to get their height.

Solution:

Use requestAnimationFrame to ensure that your code is run after the painting of your newly rendered object

scrollElement: function() {
  // Store a 'this' ref, and
  var _this = this;
  // wait for a paint before running scrollHeight dependent code.
  window.requestAnimationFrame(function() {
    var node = _this.getDOMNode();
    if (node !== undefined) {
      node.scrollTop = node.scrollHeight;
    }
  });
},
componentDidMount: function() {
  this.scrollElement();
},
// and or
componentDidUpdate: function() {
  this.scrollElement();
},
// and or
render: function() {
  this.scrollElement()
  return [...]

From the ReactDOM.render() documentation:

If the optional callback is provided, it will be executed after the component is rendered or updated.


I feel that this solution is dirty, but here we go:

componentDidMount() {
    this.componentDidUpdate()
}

componentDidUpdate() {
    // A whole lotta functions here, fired after every render.
}

Now I am just going to sit here and wait for the down votes.


A little bit of update with ES6 classes instead of React.createClass

import React, { Component } from 'react';

class SomeComponent extends Component {
  constructor(props) {
    super(props);
    // this code might be called when there is no element avaliable in `document` yet (eg. initial render)
  }

  componentDidMount() {
    // this code will be always called when component is mounted in browser DOM ('after render')
  }

  render() {
    return (
      <div className="component">
        Some Content
      </div>
    );
  }
}

Also - check React component lifecycle methods:The Component Lifecycle

Every component have a lot of methods similar to componentDidMount eg.

  • componentWillUnmount() - component is about to be removed from browser DOM

I am currently using hooks.
Something like this:

import React, { useEffect } from 'react'


const AppBase = ({ }) => {

    useEffect(() => {
        // set el height and width etc.
    }, [])

    return (
        <div className="wrapper">
            <Sidebar />
            <div className="inner-wrapper">
                <ActionBar title="Title Here" />
                <BalanceBar balance={balance} />
                <div className="app-content">
                    <List items={items} />
                </div>
            </div>
        </div>
    );
}

export default AppBase

For me, no combination of window.requestAnimationFrame or setTimeout produced consistent results. Sometimes it worked, but not always—or sometimes it would be too late.

I fixed it by looping window.requestAnimationFrame as many times as necessary.
(Typically 0 or 2-3 times)

The key is diff > 0: here we can ensure exactly when the page updates.

// Ensure new image was loaded before scrolling
if (oldH > 0 && images.length > prevState.images.length) {
    (function scroll() {
        const newH = ref.scrollHeight;
        const diff = newH - oldH;

        if (diff > 0) {
            const newPos = top + diff;
            window.scrollTo(0, newPos);
        } else {
            window.requestAnimationFrame(scroll);
        }
    }());
}

There is actually a lot simpler and cleaner version than using request animationframe or timeouts. Iam suprised no one brought it up: the vanilla-js onload handler. If you can, use component did mount, if not, simply bind a function on the onload hanlder of the jsx component. If you want the function to run every render, also execute it before returning you results in the render function. the code would look like this:

_x000D_
_x000D_
runAfterRender = () => _x000D_
{_x000D_
  const myElem = document.getElementById("myElem")_x000D_
  if(myElem)_x000D_
  {_x000D_
    //do important stuff_x000D_
  }_x000D_
}_x000D_
_x000D_
render()_x000D_
{_x000D_
  this.runAfterRender()_x000D_
  return (_x000D_
    <div_x000D_
      onLoad = {this.runAfterRender}_x000D_
    >_x000D_
      //more stuff_x000D_
    </div>_x000D_
  )_x000D_
}
_x000D_
_x000D_
_x000D_

}


After render, you can specify the height like below and can specify the height to corresponding react components.

render: function () {
    var style1 = {height: '100px'};
    var style2 = { height: '100px'};

   //window. height actually will get the height of the window.
   var hght = $(window).height();
   var style3 = {hght - (style1 + style2)} ;

    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar style={style1} title="Title Here" />
          <BalanceBar style={style2} balance={balance} />
          <div className="app-content" style={style3}>
            <List items={items} />
          </div>
        </div>
      </div>
    );`
  }

or you can specify the height of the each react component using sass. Specify first 2 react component main div's with fixed width and then the third component main div's height with auto. So based on the third div's content the height will be assigned.


I ran into the same problem.

In most scenarios using the hack-ish setTimeout(() => { }, 0) in componentDidMount() worked.

But not in a special case; and I didn't want to use the ReachDOM findDOMNode since the documentation says:

Note: findDOMNode is an escape hatch used to access the underlying DOM node. In most cases, use of this escape hatch is discouraged because it pierces the component abstraction.

(Source: findDOMNode)

So in that particular component I had to use the componentDidUpdate() event, so my code ended up being like this:

componentDidMount() {
    // feel this a little hacky? check this: http://stackoverflow.com/questions/26556436/react-after-render-code
    setTimeout(() => {
       window.addEventListener("resize", this.updateDimensions.bind(this));
       this.updateDimensions();
    }, 0);
}

And then:

componentDidUpdate() {
    this.updateDimensions();
}

Finally, in my case, I had to remove the listener created in componentDidMount:

componentWillUnmount() {
    window.removeEventListener("resize", this.updateDimensions.bind(this));
}

Just to update a bit this question with the new Hook methods, you can simply use the useEffect hook:

import React, { useEffect } from 'react'

export default function App(props) {

     useEffect(() => {
         // your post layout code (or 'effect') here.
         ...
     },
     // array of variables that can trigger an update if they change. Pass an
     // an empty array if you just want to run it once after component mounted. 
     [])
}

Also if you want to run before the layout paint use the useLayoutEffect hook:

import React, { useLayoutEffect } from 'react'

export default function App(props) {

     useLayoutEffect(() => {
         // your pre layout code (or 'effect') here.
         ...
     }, [])
}

I am not going to pretend I know why this particular function works, however window.getComputedStyle works 100% of the time for me whenever I need to access DOM elements with a Ref in a useEffect — I can only presume it will work with componentDidMount as well.

I put it at the top of the code in a useEffect and it appears as if it forces the effect to wait for the elements to be painted before it continues with the next line of code, but without any noticeable delay such as using a setTimeout or an async sleep function. Without this, the Ref element returns as undefined when I try to access it.

const ref = useRef(null);

useEffect(()=>{
    window.getComputedStyle(ref.current);
    // Next lines of code to get element and do something after getComputedStyle().
});

return(<div ref={ref}></div>);

I'm actually having a trouble with similar behaviour, I render a video element in a Component with it's id attribute so when RenderDOM.render() ends it loads a plugin that needs the id to find the placeholder and it fails to find it.

The setTimeout with 0ms inside the componentDidMount() fixed it :)

componentDidMount() {
    if (this.props.onDidMount instanceof Function) {
        setTimeout(() => {
            this.props.onDidMount();
        }, 0);
    }
}

In my experience window.requestAnimationFrame wasn't enough to ensure that the DOM had been fully rendered / reflow-complete from componentDidMount. I have code running that accesses the DOM immediately after a componentDidMount call and using solely window.requestAnimationFrame would result in the element being present in the DOM; however, updates to the element's dimensions aren't reflected yet since a reflow hasn't yet occurred.

The only truly reliable way for this to work was to wrap my method in a setTimeout and a window.requestAnimationFrame to ensure React's current call stack gets cleared before registering for the next frame's render.

function onNextFrame(callback) {
    setTimeout(function () {
        requestAnimationFrame(callback)
    })
}

If I had to speculate on why this is occurring / necessary I could see React batching DOM updates and not actually applying the changes to the DOM until after the current stack is complete.

Ultimately, if you're using DOM measurements in the code you're firing after the React callbacks you'll probably want to use this method.


You can change the state and then do your calculations in the setState callback. According to the React documentation, this is "guaranteed to fire after the update has been applied".

This should be done in componentDidMount or somewhere else in the code (like on a resize event handler) rather than in the constructor.

This is a good alternative to window.requestAnimationFrame and it does not have the issues some users have mentioned here (needing to combine it with setTimeout or call it multiple times). For example:

class AppBase extends React.Component {
    state = {
        showInProcess: false,
        size: null
    };

    componentDidMount() {
        this.setState({ showInProcess: true }, () => {
            this.setState({
                showInProcess: false,
                size: this.calculateSize()
            });
        });
    }

    render() {
        const appStyle = this.state.showInProcess ? { visibility: 'hidden' } : null;

        return (
            <div className="wrapper">
                ...
                <div className="app-content" style={appStyle}>
                    <List items={items} />
                </div>
                ...
            </div>
        );
    }
}

React has few lifecycle methods which help in these situations, the lists including but not limited to getInitialState, getDefaultProps, componentWillMount, componentDidMount etc.

In your case and the cases which needs to interact with the DOM elements, you need to wait till the dom is ready, so use componentDidMount as below:

/** @jsx React.DOM */
var List = require('../list');
var ActionBar = require('../action-bar');
var BalanceBar = require('../balance-bar');
var Sidebar = require('../sidebar');
var AppBase = React.createClass({
  componentDidMount: function() {
    ReactDOM.findDOMNode(this).height = /* whatever HEIGHT */;
  },
  render: function () {
    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar title="Title Here" />
          <BalanceBar balance={balance} />
          <div className="app-content">
            <List items={items} />
          </div>
        </div>
      </div>
    );
  }
});

module.exports = AppBase;

Also for more information about lifecycle in react you can have look the below link: https://facebook.github.io/react/docs/state-and-lifecycle.html

getInitialState, getDefaultProps, componentWillMount, componentDidMount