Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to render loading state after immediately clicking a link, but before data has been fetched #46

Closed
garthk opened this issue Jun 25, 2014 · 11 comments

Comments

@garthk
Copy link

garthk commented Jun 25, 2014

I want to give my users instant response to <Link> activation, then have the route handler component dispatch an event to have its store loaded according to the params.

I'm also in the habit of injecting dependencies in props. Sneaking in the dependencies as enclosed variables is easy when every component is in the same file, but feels wrong the moment you put a component in its own file:

var Component = require('../component.jsx')(stores, dispatcher.fire);
React.renderComponent(Route({ handler: Component }), document.body);

I'd prefer this:

var Component = require('../component.jsx');

React.renderComponent(
    Route({ handler: Component,
            stores: stores,
            fire: dispatcher.fire  }
    ), document.body);

Component gets stores and fire as RNR-style "static props"; it can start watching the stores in componentDidMount, and fire events from its event handlers. All we need is fire that first event to load the data. So, in willTransitionTo, we calls fire on the dispatcher…

… except, we can't. There's no such argument, and we can't provide component like willTransitionFrom gets because the component hasn't been created yet. Indeed, we want to handle this before the component is created so we can avoid a flash of not-even-trying-to-load unanticipated state. That leaves the route's static props as a way to provide willTransitionFrom access to the dependencies.

Am I missing some cleaner way to accomplish this?

@garthk
Copy link
Author

garthk commented Jun 25, 2014

Hack-around: the components used as a route handler being kinda special, anyway, I'm using them as minimal life support around the domain-specific components. The Route creates the NounPage, which creates the Noun.

NounPage can use its getInitialState to peek at the store it's passing to Noun and, if necessary, use the fire method it's also passing to Noun to ask the store to start loading the content.

My dispatcher is synchronous, so the store's state will show that it's loading the data by the time Page hits its own getInitialState and asks the store for its state.

@ryanflorence
Copy link
Member

if I understand you correctly, you want a route handler to display some sort of loading UI while you fetch data?

var SomethingHandler = React.createClass({
  getInitialState: function() {
    return { loading: true, something: {} };
  },

  componentDidMount: function() {
    someStore.fetch(this.props.params.id, function(something) {
      this.setState({ loading: false, something: something });
    }.bind(this));
  },

  render: function() {
    if (this.state.loading) {
      return <div>loading ...</div>;
    }
    else {
      return <Something something={this.state.something}/>
    }
  }
});

Am I missing your question?

Edit: s/componentWillMount/componentDidMount/

@ryanflorence
Copy link
Member

Also, you can pass props into routes and they'll get sent down to your handlers:

<Route handler={App} store={store}/>

// ...
var App = React.createClass({
  componentDidMount: function() {
    this.props.store.whatev();
  }
});

@garthk
Copy link
Author

garthk commented Jun 25, 2014

componentWillMount makes the most sense. Cheers!

@garthk garthk closed this as completed Jun 25, 2014
@ryanflorence ryanflorence changed the title Design: pass willTransitionTo a reference to the route's static props How to render loading state after immediately clicking a link, but before data has been fetched Jun 25, 2014
@minwe
Copy link

minwe commented Apr 30, 2015

Hi, all:

I create a mixin to display loading UI like this:

var NProgress = require('nprogress');

var ProgressMixin = {
  componentWillMount: function() {
    NProgress.start();
  },

  componentDidMount: function() {
    NProgress.done();
  }
};

Then add the mixin to every page:

var Page1 = React.createClass({
  mixins: [ProgressMixin],

  render: function() {
    return (
        <div className="Image">
          <h1>Page 1</h1>
        </div>
    );
  }
});

It works! When change the link, the loading UI appear.

But I have to add the mixin to every handler, is there any API for adding mixin to all route handlers?

Thanks very much.

@uragecz
Copy link

uragecz commented Sep 8, 2016

@minwe Hi, i am trying your solution but desnt work. I am writting in ES6, so its little different.

class Print extends React.Component {

  constructor(props)
  {
    super(props);
    this.state = {
      canvas: null,
      mixins: [ProgressMixin],
    }
    render(){
       return(
         <canvas className="canvas" ref="canvas" width="895" height="560"></canvas>
       )
    }
}

and mixin looks same. -

var NProgress = require('nprogress');

var ProgressMixin = {
    componentWillMount: function() {
        NProgress.start();
    },

    componentDidMount: function() {
        console.log('neco')
        NProgress.done();
    },

    start(){
        console.log('bla');
        NProgress.start();
    },
    stop(){
        NProgress.done();
    }
};

module.exports = ProgressMixin;

But still there is long time to render new page without any loading UI. I am rendering canvas and it takes a lot of time. I tried to get into start method console.log function, but its not invoken. So i think this method isnt called. Any tips ?

@minwe
Copy link

minwe commented Sep 9, 2016

@uragecz

Mixins not work with ES6 Class, see https://facebook.github.io/react/docs/reusable-components.html#no-mixins .

You should use createClass.

@uragecz
Copy link

uragecz commented Sep 9, 2016

@minwe createClass needs render method, so have i write just emty div ? If yes, it still doesnt work :/ i changed it to -

var ProgressMixin = React.createClass({
    componentWillMount: function() {
        NProgress.start();
    },

    componentDidMount: function() {
        NProgress.done();
    },

    start: function(){

        NProgress.start();
    },
    stop: function(){
        NProgress.done();
    },

    render: function(){
        return(<div></div>)
    }
});

module.exports = ProgressMixin;

@uragecz
Copy link

uragecz commented Sep 9, 2016

Or u think to do remake my component which calls mixins: [ProgressMixin] ?

@roby-rodriguez
Copy link

roby-rodriguez commented Nov 16, 2016

or you could use babel es6 decorators if you're working with webpack

create a file decorators/nProgress.jsx:

import NProgress from 'nprogress'

export default function nProgress(target) {
    const superComponentWillMount = target.prototype.componentWillMount
    const superComponentDidMount = target.prototype.componentDidMount
    target.prototype.componentWillMount = function() {
        if (typeof superComponentWillMount === 'function')
            superComponentWillMount.apply(this, arguments)
        NProgress.start()
    }
    target.prototype.componentDidMount = function() {
        if (typeof superComponentDidMount === 'function')
            superComponentDidMount.apply(this, arguments)
        NProgress.done()
    }
}

then simply annotate your components with "nProgress" like so:

import React, { PropTypes, Component } from 'react'
import { Link } from "react-router"
import { Jumbotron } from 'react-bootstrap'
import nProgress from '../decorators/nProgress'

@nProgress
export default class Reports extends Component {
...

@Quadriphobs1
Copy link

@roby-rodriguez Please am sorry to ask this, when you mentioned using annotate I am really confused if I might have to set my project to static type(flow) or typescript as it is not working with normal js. I am using CRA and I dont want to eject is there a way I could modify the append to babelrc without ejecting to configure babel for the annotate

@lock lock bot locked as resolved and limited conversation to collaborators Aug 29, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants