475

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;
Oscar Godson
  • 31,662
  • 41
  • 121
  • 201
  • An alternative to doing this programatically is to just rely on css, you can use a combination of flex, grow, and height: 100vh so that the elements always span exactly the size of the screen – ed__ Oct 20 '22 at 22:35

20 Answers20

349

componentDidMount()

This method is called once after your component is rendered. So your code would look like so.

var AppBase = React.createClass({
  componentDidMount: function() {
    var $this = $(ReactDOM.findDOMNode(this));
    // set el height and width etc.
  },

  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>
    );
  }
});
Edgar
  • 6,022
  • 8
  • 33
  • 66
Harborhoffer
  • 5,796
  • 3
  • 18
  • 10
  • 240
    or `componentDidUpdate` if the values can change after the first render. – zackify Oct 27 '14 at 22:24
  • 6
    I'm trying to change a css property which is set to transition, so that the animation begins after rendering. Unfortunately, changing the css in `componentDidMount()` does not cause a transition. – eye_mew Nov 13 '14 at 11:20
  • 8
    Thanks. The name is so intuitive that I wonder why I was trying ridiculous names like "init" or even "initialize". – Pawel May 08 '15 at 09:20
  • 16
    Changing it in componentDidMount is too fast for the browser. Wrap it in a setTimeout and give it no actual time. i.e. `componentDidMount: () => { setTimeout(addClassFunction())}`, or use rAF, the answer below this provides this answer. –  Jan 26 '16 at 21:25
  • 5
    This most certainly does NOT work. If you get a node list and then try to iterate over the node list you will find the length is equal to 0. Doing setTimeout and waiting for 1 second worked for me. Unfortunately it doesn't appear react has a method that truly waits until after the DOM is rendered. – NickJ Jan 31 '19 at 04:14
  • 1
    @Jhawins - You can do these calculations in the setState callback instead, which is more reliable since it is guaranteed to occur after render - see [my answer below](https://stackoverflow.com/questions/26556436/react-after-render-code/51639584#51639584) – Joseph238 May 14 '19 at 21:01
  • 1
    @Joseph238 it has been a few years since my comment but I have since learned to do it properly (as you mentioned). Cheers! –  May 14 '19 at 21:33
  • Amazing. I have spent all day trying to get my MDL Lite checkbox component to style correctly, after being dynamically rendered. This method solved the problem immediately. I just added: componentHandler.upgradeDom(); inside the componentDidMount() method. – Charles Robertson Dec 17 '20 at 23:49
  • This is NOT the answer to the question, not at all. as others have commented. `componentDidMount` is NOT fired when the browser has completed rendering the DOM on screen. It's fired when react is done, which is not the same thing. i.e. your nodes WILL exist. But they will have a width and height of 0, since they are not actually rendered on screen yet. So you need an ugly timeout to give it time to exist before your code can grab any node dimensions. You CAN use `componentDidUpdate` if you pass in a prop that changes after it has rendered. – Skäggiga Mannen Oct 05 '21 at 13:09
280

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 [...]
Graham P Heath
  • 7,009
  • 3
  • 31
  • 45
  • React.findDOMNode(this) returns null in the callback of requestAnimationFrame when invoked inside componentDidMount, for me. – Daniel Coffman Oct 05 '15 at 18:46
  • @DanielCoffman could you use bind? http://stackoverflow.com/questions/6065169/requestanimationframe-with-this-keyword – Graham P Heath Oct 05 '15 at 22:36
  • 41
    window.requestAnimationFrame was not enough for me. I had to hack it with window.setTimeout. Argggghhhhhh!!!!! – Alex Oct 30 '15 at 13:39
  • Worked for me. Needed to trigger the render of a twitter button in a modal. – frostymarvelous Nov 02 '15 at 02:08
  • 2
    Odd. Maybe it has changed in most recent version of React, I don't think the call to requestAnimationFrame is necessary. The documentation says: " Invoked immediately after the component's updates are flushed to the DOM. This method is not called for the initial render. Use this as an opportunity to operate on the DOM when the component has been updated. " ... i.e., it is flushed, the DOM node should be present. -- https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate – Jim Soho Jan 15 '16 at 03:42
  • 2
    @JimSoho, I hope you're right that this was fixed, but there's not actually anything new in that documentation. This is for edge cases where the dom being updated isn't enough, and it's important that we wait for the paint cycle. I tried to create a fiddle with the new versions and the old, but I couldnt seem to create a complex enough component to demonstrate the issue, even going back a few versions... – Graham P Heath Jan 19 '16 at 06:08
  • i need to show a toast message, and the problems lifecycle stop in render and don't follow ComponentDidMount, how to solve it ? – stackdave Oct 19 '17 at 11:11
  • @Alex I used a loop with `window.requestAnimationFrame` to know exactly when the DOM updates: https://stackoverflow.com/a/50206430/1313757 – jackcogdill May 07 '18 at 02:49
  • @GrahamPHeath your solution worked for me but i don't understand completely how it works. Can you tell me where you got this information : "requestAnimationFrame waits for the current rendering to finish" – neptunian May 24 '18 at 23:19
  • 4
    @neptunian Strictly speaking"[RAF] is called [...] before the next repaint..." -- [ https://developer.mozilla.org/en-US/Apps/Fundamentals/Performance/CSS_JavaScript_animation_performance#requestAnimationFrame ]. In this situation the node still needs to have its layout calculated by the DOM (aka "reflowed"). This uses RAF as a way of jumping from before the layout to after the layout. The browser documentation from Elm is a good place for more: http://elmprogramming.com/virtual-dom.html#how-browsers-render-html – Graham P Heath May 25 '18 at 15:53
  • Adding `node.addEventListener` with the relevant event type (say, `transitionend`) **and** with the `{once: true}` parameter (we only want to invoke this method once) did the job for me. No need for `window.requestAnimationFrame` nor `setTimeout`. – Eyal Roth Jul 03 '19 at 19:24
  • 3
    `_this.getDOMNode is not a function` what the heck is this code? – OZZIE Feb 11 '20 at 09:48
  • @OZZIE It's an answer from 2015, that's what it is. I think its functionally similar to `findDOMNode` via https://stackoverflow.com/questions/29527309/react-0-13-this-getdomnode-equivalent-to-react-finddomnode – Graham P Heath Apr 21 '20 at 16:48
  • 1
    Thank you for the answer. This is still relevant in May 2021. I had a situation where I am using `measureInWindow` to get window dimensions, and I need it to happen after rendering (and painting). Using anything based on `onDidMount()` or `onLayout()` consistently resulted in dimensions of zero on iOS (whereas on Android it worked), but using the `requestAnimationFrame()` method described here works on iOS, too. – jogojapan May 09 '21 at 09:44
  • @Alex I happen to have a setState() in at the page refresh - so I put the setState() in the first line of the window.requestAnimationFrame callback function. Other things are behind the setState() while still inside requestAnimationFrame callback. It worked. Maybe try an empty setState()? Of course, I had an "if" clause wrapping the requestAnimationFrame as the stopping criterion. Otherwise it gets into an infinite loop of "update state -> get into componentDidUpdate -> update state again -> ..." – Yan Yang Jun 25 '21 at 06:18
121

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.

Elliot Chong
  • 1,410
  • 1
  • 9
  • 10
  • 1
    You only need the setTimeout OR the requestAnimationFrame, not both. –  Jan 26 '16 at 21:27
  • Also no need to pass `0` to setTimeout if you're not giving it a time. –  Jan 26 '16 at 21:27
  • 10
    Typically- you're correct. However, in the context of React's componentDidMount method if you attach a requestAnimationFrame before that stack is finished the DOM may not actually be fully updated. I have code that consistently reproduces this behavior within the context of React's callbacks. The only way to be sure your code is executing (once again, in this specific React use-case) after the DOM has updated is letting the call stack clear first with a setTimeout. – Elliot Chong Feb 05 '16 at 02:24
  • 6
    You'll notice other comments above which mention needing the same workaround, i.e.: http://stackoverflow.com/questions/26556436/react-after-render-code/34999925#comment54662760_28748160 This is the only 100% reliable method for this React use-case. If I had to venture a guess it may be due to React batching updates themselves which potentially don't get applied within the current stack (hence deferring the requestAnimationFrame to the next frame to ensure the batch is applied). – Elliot Chong Feb 05 '16 at 02:26
  • 4
    I think you might need to brush up on your JS internals... http://altitudelabs.com/blog/what-is-the-javascript-event-loop/ http://stackoverflow.com/questions/8058612/does-calling-settimeout-clear-the-callstack – Elliot Chong Feb 06 '16 at 17:46
  • 2
    Would this be better as a nested `requestAnimationFrame` call? Eg; `function onNextFrame(cb) { window.requestAnimationFrame(_ => window.requestAnimationFrame(cb)) }`. Based on the spec (https://html.spec.whatwg.org/multipage/webappapis.html#animation-frames), this would guarantee it runs on the very next frame after the initial render (in particular, check out the order of executing the list in "run the animation frame callback"). It avoids the ambiguity of when setTimeout will be executed wrt the next frame. – Jess Telford Jul 12 '16 at 01:46
  • 1
    using only requestAnimationFrame didn't work for me, but this solution did. Thank you! – omerts Dec 29 '16 at 17:21
  • 1
    This technique didn't work for me. It's like the DOM still isn't laid out at the time the callback is made. Because if I do a quick test setting the timeout to 1000 I get the correct dimensions. – sleep May 10 '17 at 04:36
  • Doesnt work here either. I position a DOM element in the render function using inline styles, based on some state. I want to get the position using getBoundingClientRect but it gives the incorect values. Indeed, when using a timeout interval of 1000 it works. – Jochie Nabuurs Dec 07 '17 at 10:38
  • I similarly needed both RAF and setTimeout in order for it to work, I was doing something inside `componentDidUpdate`. – Andrew Smith Jun 06 '19 at 03:01
  • Sadly, this is the only thing that worked for me as well. – Piotr Jan 23 '20 at 05:54
  • no fix here... I'm trying to do a `scrollIntoView` on my element. It just won't work on the initial rendering of my page. Logging shows that it's called, but the DOM elements report size 0. It works fine on subsequent re-renders. – vicmortelmans May 17 '20 at 17:18
  • When I see such solutions I really would like to go back to Vanilla JS, some MooTools and/or writing jQuery plugins... This should not happen in React. Or React should give official API to handle such cases. – Marecky Oct 09 '20 at 08:42
41

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.
         ...
     }, [])
}
P Fuster
  • 2,224
  • 1
  • 20
  • 30
  • 1
    As per React's documentation, useLayoutEffect happens _after_ all DOM mutations https://reactjs.org/docs/hooks-reference.html#uselayouteffect – Philippe Hebert Jan 06 '20 at 16:05
  • 3
    True, but it does run before the layout has a chance to paint `Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.` I'll edit. – P Fuster Jan 06 '20 at 16:23
  • Do you happen to know if useEffect runs after the browser's reflow (not what React calls 'paint')? Is it safe to request an element's scrollHeight with useEffect? – eMontielG Feb 06 '20 at 17:07
  • 1
    It's safe for useEffect – P Fuster Feb 06 '20 at 17:32
  • yes, refactoring my component from class and using useEffect worked for me – orszaczky Sep 01 '20 at 09:26
20

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>
        );
    }
}
Joseph238
  • 1,174
  • 1
  • 14
  • 22
13

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.

Jaakko Karhu
  • 2,298
  • 4
  • 28
  • 41
11

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

Alireza
  • 100,211
  • 27
  • 269
  • 172
  • my component did mount runs before the page renders causing a big delay as an api call loads in data. – Jason G Apr 02 '19 at 18:10
8

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));
}
Edgar
  • 6,022
  • 8
  • 33
  • 66
theRonny
  • 385
  • 5
  • 20
6

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:

runAfterRender = () => 
{
  const myElem = document.getElementById("myElem")
  if(myElem)
  {
    //do important stuff
  }
}

render()
{
  this.runAfterRender()
  return (
    <div
      onLoad = {this.runAfterRender}
    >
      //more stuff
    </div>
  )
}

}

Farantir
  • 61
  • 1
  • 1
4

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);
    }
}
Barceyken
  • 95
  • 4
3

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.

H. Pauwelyn
  • 13,575
  • 26
  • 81
  • 144
3

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);
        }
    }());
}
jackcogdill
  • 4,900
  • 3
  • 30
  • 48
2

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>);
Total Legend
  • 51
  • 2
  • 3
2

I recommend that you make use of hooks.
They are available from version 16.8.0 onwards.

You can check the behavior of this hook in the official react documentation.

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
Xabi
  • 95
  • 8
2

for functional components you can react-use-call-onnext-render, its a custom hook that allows schedule callback on a later render.

It is used successfully on one of my other projects.

for requiring dimension of a dom element, see this example,its the third example on react-use-call-onnext-render examples:

let's say we want to get dimensions of a removable DOM element,lets say div that is controlled by showBox state variable. for that we can use getBoundingClientRect(). however, we want to call this function only after the element mounted into the dom, so will schedule this call one render after the variable responsible for showing this element in the dom has changed,and this variable is showBox, so he will be dependency of useCallOnNextRender:

const YourComponent = () => {
    const [showBox, setShowBox] = useState(false)
    const divRef = useRef()
    const callOnNextShowBoxChange = useCallOnNextRender()
    return (
        <>
            <div style={canvasStyle} id="canvas">
                <button style={boxStyle} onClick={() => {
                    setShowBox(!showBox)
                    callOnNextShowBoxChange(() => console.log(divRef.current.getBoundingClientRect())) //right value
                }}>toggle show box
                </button>
                <div style={{border: "black solid 1px"}} ref={divRef}>
                    {showBox ? <div style={boxStyle}>box2</div> : null}
                </div>
            </div>
        </>
    );
};
Eliav Louski
  • 3,593
  • 2
  • 28
  • 52
2

For me, componentDidUpdate alone or window.requestAnimationFrame alone didn't solve the problem, but the following code worked.

// Worked but not succinct
    componentDidUpdate(prevProps, prevState, snapshot) {
        if (this.state.refreshFlag) {  // in the setState for which you want to do post-rendering stuffs, set this refreshFlag to true at the same time, to enable this block of code.
            window.requestAnimationFrame(() => {
                this.setState({
                    refreshFlag: false   // Set the refreshFlag back to false so this only runs once.
                });
                something = this.scatterChart.current.canvas
                    .toDataURL("image/png");  // Do something that need to be done after rendering is finished. In my case I retrieved the canvas image.
            });
        }
    }

And later I tested with requestAnimationFrame commented, it still worked perfectly:

// The best solution I found
    componentDidUpdate(prevProps, prevState, snapshot) {
        if (this.state.refreshFlag) {  // in the setState for which you want to do post-rendering stuffs, set this refreshFlag to true at the same time, to enable this block of code.
            // window.requestAnimationFrame(() => {
                this.setState({
                    refreshFlag: false   // Set the refreshFlag back to false so this only runs once.
                });
                something = this.scatterChart.current.canvas
                    .toDataURL("image/png");  // Do something that need to be done after rendering is finished. In my case I retrieved the canvas image.
            // });
        }
    }

I'm not sure whether it's just a coincidence that the extra setState induced a time delay, so that when retrieving the image, the drawing is already done (I will get the old canvas image if I remove the setState).

Or more possibly, it was because setState is required to be executed after everything is rendered, so it forced the waiting for the rendering to finish.

-- I tend to believe the latter, because in my experience, calling setState consecutively in my code will result in each one triggered only after the last rendering finished.

Lastly, I tested the following code. If this.setState({}); doesn't update the component, but wait till the rendering finishes, this would be the ultimate best solution, I thought. However, it failed. Even when passing an empty {}, setState() still updates the component.

// This one failed!
    componentDidUpdate(prevProps, prevState, snapshot) {
        // if (this.state.refreshFlag) {
            // window.requestAnimationFrame(() => {
                this.setState({});
                something = this.scatterChart.current.canvas
                    .toDataURL("image/png");
            // });
        // }
    }
Yan Yang
  • 1,804
  • 2
  • 15
  • 37
1

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).

Anatoly Strashkevich
  • 1,834
  • 4
  • 16
  • 32
1

After trying all the suggested solutions above with no luck I found one of my elements in the middle had CSS transition, that's why I failed to get correct computed geometry after props changed. So I had to use onTransitionEnd listener to wait for a moment when to try getting the computed by DOM height of container element. Hope this will save someone's work day lol.

Yuri Gor
  • 1,353
  • 12
  • 26
-1

From the ReactDOM.render() documentation:

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

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Juampy NR
  • 2,618
  • 1
  • 25
  • 21
  • 9
    can you add an example of how to use this? I mostly return elements from the render method, I don't call render and provide values. – dcsan Dec 20 '15 at 23:48
  • 23
    Unfortunately the callback you mention is only available in the for [the toplevel `ReactDOM.render`](https://facebook.github.io/react/docs/top-level-api.html#reactdom.render), not for the [component level's `ReactElement.render`](https://facebook.github.io/react/docs/component-specs.html#render) (which is the subject here). – Bramus Jul 12 '16 at 09:02
  • 1
    Example here would be helpful – DanV Jun 07 '17 at 09:30
  • 2
    I clicked on the link in your answer, and I couldn't find the line you quoted, and your answer does not include enough information to work from without it. Please see https://stackoverflow.com/help/how-to-answer for advice on how to write a good question – Benubird Jan 23 '19 at 16:35
-1

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
Edgar
  • 6,022
  • 8
  • 33
  • 66
Adam Pietrasiak
  • 12,773
  • 9
  • 78
  • 91
  • 3
    No disrespect, but how does this answer the question? Showing an update on ES6 isn't really related to the question / doesn't change anything. All of the much older answers already talk about how componentDidMount does not work on its own. – dave4jr Jun 02 '19 at 09:49