react-router
Version:
A complete routing library for React.js
116 lines (97 loc) • 3.17 kB
JavaScript
import React from 'react';
function shallowEqual(a, b) {
var ka = 0;
var kb = 0;
for (let key in a) {
if (a.hasOwnProperty(key) && a[key] !== b[key])
return false;
ka++;
}
for (let key in b)
if (b.hasOwnProperty(key))
kb++;
return ka === kb;
}
var AsyncProps = React.createClass({
statics: {
createElement (Component, state) {
return Component.loadProps ?
<AsyncProps Component={Component} routing={state}/> :
<Component {...state}/>;
}
},
getInitialState() {
return {
propsAreLoading: false,
propsAreLoadingLong: false,
asyncProps: null,
previousRoutingState: null
};
},
componentDidMount() {
this.load(this.props);
},
componentWillReceiveProps(nextProps) {
var needToLoad = !shallowEqual(
nextProps.routing.routeParams,
this.props.routing.routeParams
);
if (needToLoad) {
var routerTransitioned = nextProps.routing.location !== this.props.routing.location;
var keepPreviousRoutingState = this.state.propsAreLoadingLong && routerTransitioned;
if (keepPreviousRoutingState) {
this.load(nextProps);
} else {
this.setState({
previousRoutingState: this.props.routing
}, () => this.load(nextProps));
}
}
},
/*
* Could make this method much better, right now AsyncProps doesn't render its
* children until it fetches data, causing a "waterfall" effect, when instead
* it could look at the branch of components from it down to the end and load
* up the props for all of them in parallel, waterfall will do for now...
*/
load(props) {
var lastLoadTime = this._lastLoadTime = Date.now();
var { params } = props.routing;
var { Component } = this.props;
this.setState({ propsAreLoading: true }, () => {
var longLoadTimer = setTimeout(() => {
this.setState({ propsAreLoadingLong: true });
}, 300);
// TODO: handle `error`s
Component.loadProps(params, (error, asyncProps) => {
clearTimeout(longLoadTimer);
// if the router transitions between now and when the callback runs we will
// ignore it to prevent setting state w/ the wrong data (earlier calls to
// load that call back later than later calls to load)
if (this._lastLoadTime !== lastLoadTime || !this.isMounted())
return;
this.setState({
propsAreLoading: false,
propsAreLoadingLong: false,
asyncProps: asyncProps,
previousRoutingState: null
});
});
});
},
render() {
var { Component } = this.props;
var { asyncProps, propsAreLoading, propsAreLoadingLong } = this.state;
var routing = this.state.previousRoutingState || this.props.routing;
if (this.state.asyncProps === null)
return Component.Loader ? <Component.Loader {...routing}/> : null;
return <Component
onPropsDidChange={() => this.load(this.props) }
propsAreLoading={propsAreLoading}
propsAreLoadingLong={propsAreLoadingLong}
{...routing}
{...asyncProps}
/>;
}
});
export default AsyncProps;