Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React waiting for map function before deciding render to prevent div flash

I am having a bit of trouble preventing a div flash where react will render the error message then receive props and finally render the props.

In my EventsView component I have the following.

view.js

var view;
if (_.size(this.props.events) !== 0) {
view =  this.props.events.map(function(year) {
                return <YearView year={year} />;
        });
} else {
    view = <NoEventsView />
}

So the variable view is rendered, initially there is no this.props.events on page load as I have another part of the file creating it. The moment this.props.events is created the events are rendered. This is done throught a react.Backbone mixin.

I have tried doing something used some of the component lifecycle methods such as componentWillUpdate. The problem is they don't seem to want to work the way I want them to work and the error message is never rendered. Example of what I have tried (among others) below

I thought of doing something like this.

view.js

componentWillUpdate: function(nextprops, nextstate) {
if (_.size(nextprops.events) !== 0) {
    var view =  nextprops.events.map(function(year) {
                    return <YearView year={year} />;
            });
    this.setState({
        view: view
    });
} else {
    this.setState({
        view: <NoEventsView />
    })
}
},

// render this.state.view

I would then set the getInitialState:view to the a this.props.events map.

*EDIT - A temporary and terrible workaround *

So the problem I was having was I had a top level component accepting a single prop which was a global variable like so

view.js

React.render(<EventsView events={theseEvents} />, document.getElementById('mainContent'));

theseEvents is actually a Backbone Collection global variable initialized in another file.

On document load theseEvents would be unpopulated so my view would flash with the <NoEventView /> and then rerender when theseEvents is then populated. This is handled by the backbone mixin shown below.

Dan's solution pointed me in the right direction. I set a global variable window.hasEvents to true or false depending on what I got back from my ajax request but by that time React was already rendering (the component would start as hasEvents would be true, by the time it's finished hasEvents might be false). Even if I made hasEvents a prop of <EventsView /> react wouldn't rerender when it changed.

So as a temporary workaround I have defaulted hasEvents to true and rendered EventsView. I then waited for the component to load and checked whether it was in sync with hasEvents. shown below. Honestly this workaround is not very pleasant.

view.js

ar EventsView = React.createBackboneClass({
mixins: [
    React.BackboneMixin("events", "all")
],

getInitialState: function() {
    return {
        hasEvents: window.hasEvents
    }
},

componentDidMount: function() {
    var model = this;
    // This waits after component loading to check whether hasEvents is the same
    setTimeout(function() {
        if(!(model.state.hasEvents == window.hasEvents)) {
            model.setState({
                hasEvents: window.hasEvents
            })
        };
    }, 1000);
},

render: function() {
    var view;
    if (this.props.events.length > 0) {
        view =  this.props.events.map(function(year) {
                        return <YearView year={year} />;
                });
    }
    if (!this.state.hasEvents)  {
        view = <NoEventsView />
    }
    return {
        // other parts of the page were also rendered here
        {view}
    }
});

essentially componentDidMount waits for a while after component mounting and then double checks a global variable. Definitely not a great workaround especially since it relies on a 1 second theseEvents population and it only works on the initial component mounting.

like image 563
DWB Avatar asked Dec 14 '25 07:12

DWB


1 Answers

Your second approach is not “the React way” (although technically it may be working) because you shouldn't put React elements into state. Only data should go there, and in render if need to figure out how that data corresponds to DOM stucture. It's also best to avoid state unless absolutely necessary.

That being said, I'm not quite sure what you're trying to achieve. Assuming you want to prevent "No Events" flash before events come through, the only way your component can do that is by knowing that data is being fetched. If, in addition to events prop, you pass isLoading boolean prop, you can do that:

render: function () {
  var isEmpty = this.props.events.length === 0;
  if (isEmpty) {
    return this.props.isLoading ?
      <p>Loading...</p> : // note you can also return null here to render nothing
      <NoEventsView />;
  }

  return this.props.events.map(function (year) {
    return <YearView year={year} />;
  });    
}
like image 152
Dan Abramov Avatar answered Dec 17 '25 00:12

Dan Abramov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!