2

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.

Community
  • 1
  • 1
Giles Butler
  • 971
  • 3
  • 14
  • 23
  • I'm not 100% clear on what your goal is. Do you want to keep your content from overflowing a container? What is the issue you're running into? – badAdviceGuy Jun 23 '15 at 02:52
  • No I want my content to horizontally scroll inside its container automatically when it is bigger than its container, like a marquee effect, somthing similar to this http://jsfiddle.net/jonathansampson/xxuxd/ – Giles Butler Jun 24 '15 at 09:44

3 Answers3

6

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.

Zaptree
  • 3,763
  • 1
  • 31
  • 26
  • 1
    This was my case, I wasted almost one day trying to find what could cuz this issue but never thought about `js` being loaded after `css`, here's how you can load css before js: https://stackoverflow.com/a/49228202/3632172 – parse Aug 09 '19 at 14:00
1

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.

newswim
  • 430
  • 3
  • 7
  • Thanks for the update @newswim. I'm not actively working on this anymore but your solution sounds really interesting! If I ever get back to working on this I'll implement your solution and report back here. Thanks – Giles Butler Oct 05 '16 at 01:51
0

You should not call this.initMarquee(); in the render() method.

In general, you should not work with the DOM at all in the render() method.

Try to call this.initMarquee(); in the componentDidMount method.

(and I really don't understand the usage of requestAnimationFrame in this case)

Byscripts
  • 2,578
  • 2
  • 18
  • 25
  • Thanks for catching that @ByScripts - you are correct and I had it setup that way, unfortunately it didn't work. As stated in the linked answer, requestAnimationFrame is used because: "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." – Giles Butler Jun 22 '15 at 14:12