React: Can't get correct width of element after render

13,458

Solution 1

One possible problem that can occur is that your CSS has not loaded yet when componentDidMount fires. This can happen with webpack and including the css in your bundle that also contains your js even if you have included the css before your component in the project.

Solution 2

There are several issues, as you've pointed out, in dealing with the virtual DOM. You definitely don't want to be attempting to use jQuery to manipulate DOM nodes and React is likely to scream at your for attempting to.

There's a library called React-Context which would do exactly what you're asking. You would expose its api to your wrapper component and then be able to listen to events on components within the virtual dom.

This library is a little dusty, however it should work with the code sample you shared.

Share:
13,458
Giles Butler
Author by

Giles Butler

Founder of Promo.ai - Passionate about product design, startups and music production.

Updated on June 04, 2022

Comments

  • Giles Butler
    Giles Butler almost 2 years

    I'm trying to setup a Marquee in React if a piece of text is greater than its container but I can't get the correct width of the container, even after the component has rendered.

    I read in another answer React “after render” code? that you have to use requestAnimationFrame which I'm trying and it's still not working.

    If I log the width of the container it shows a width of 147px which is set using min-width in the stylesheet but the correct width should be 320px which is set using a media query when the screens min-width is 600px.

    This is a child component, the parent is rendered inside an iFrame if it makes any difference and the iFrame's width is well over 600px.

    The JS:

    module.exports = React.createClass({
    
      componentDidUpdate: function () {
        // Setup marquee
        this.initMarquee();
      },
    
      render: function () {
        // Setup marquee
        this.initMarquee();
    
        var artistName = this.props.artist.artistName;
        var trackName  = this.props.track.trackName;
    
        return (
          <div className="MV-player-trackData no-select" ref="mvpTrackData">
            <div className="MV-player-trackData-marquee-wrap" ref="mvpMarqueeWrap">
              <div className="MV-player-trackData-marquee" ref="mvpMarquee">
                <a className="MV-player-trackData-link no-select" href={this.props.storeUrl} target="_blank">
                  <span id="mvArtistName">{artistName}</span> &ndash; <span id="mvTrackName">{trackName}</span>
                </a>
              </div>
            </div>
          </div>
        )
      },
    
      initMarquee: function () {
        if ( typeof requestAnimationFrame !== 'undefined' ) {
          //store a this ref, and
          var self = this;
          //wait for a paint to setup marquee
          window.requestAnimationFrame(function() {
            self.marquee();
          });
        }
        else {
          // Suport older browsers
          window.setTimeout(this.marquee, 2000);
        }
      },
    
      marquee: function () {
        var marquee     = React.findDOMNode(this.refs.mvpMarquee);
        var marqueeWrap = React.findDOMNode(this.refs.mvpMarqueeWrap);
    
        // If the marquee is greater than its container then animate it
        if ( marquee.clientWidth > marqueeWrap.clientWidth ) {
          marquee.className += ' is-animated';
        }
        else {
          marquee.className = marquee.className.replace('is-animated', '');
        }
      }
    
    });
    

    The CSS:

    .MV-player-trackData-marquee-wrap {
      height: 20px;
      line-height: 20px;
      min-width: 147px;
      overflow: hidden;
      position: relative;
      width: 100%;
    
      @media only screen and (min-width : 600px) {
        min-width: 320px;
      }
    }
    

    I've tried a number of different solutions including laidout and react-component-width-mixin but neither of them work. I tried react component width mixin because in another part of my app I'm trying to get the value of window.innerWidth but that also returns 0 after rendering, unless I set a timeout for around 2 seconds, unfortunately though sometimes 2 seconds isn't long enough due to data loading and other callbacks so this can brake easily.

    Any help is really appreciated. Thanks.

    Update: One of the answers correctly pointed out i should be calling this.initMarquee(); inside componentDidMount which I was doing, unfortunately I pasted the wrong code from when I was testing to see if it made a difference calling it inside render. The correct code looks like this:

    componentDidMount: function () {
      // Setup marquee
      this.initMarquee();
    },
    

    Unfortunately this doesn't work either, I still receive the incorrect width for marqueeWrap.

    Update: 24/06/2015 Just to clarify, this is the marquee effect I'm trying to achieve, only when the text is bigger than its container as it's pointless scrolling it when it is not bigger.

    Also here is a link to a Github Issue from the React team speaking about why React renders before the browser paints. - So as that is the case, I want to know how do I reliably get the width of the element in question.