UNPKG

reactmvc

Version:

M(immutable model)V(view component)C(react-router)

176 lines (155 loc) 5.55 kB
/* eslint-disable react/no-multi-comp, import/no-unresolved */ import React, { Component, PropTypes } from 'react' import RouterContext from 'react-router/lib/RouterContext' function eachComponents(components, iterator) { components.forEach(value => { if (typeof value === 'object') { for (const component of value) { iterator(component) } } else { iterator(value) } }) } function findAsyncComponents(components) { const asyncComponents = [] eachComponents(components, component => { if (component.loadProps) { asyncComponents.push(component) } }) return asyncComponents } function loadAsyncProps(components, params, location) { const componentArray = [] const propsArray = [] const tasks = findAsyncComponents(components).map((component, index) => Promise.resolve().then(() => component.loadProps(params, location)).then(props => { propsArray[index] = props componentArray[index] = component }) ) return Promise.all(tasks).then(() => ({ componentArray, propsArray })) } function lookupPropsForComponent(component, propsAndComponents) { const { componentArray, propsArray } = propsAndComponents const index = componentArray.indexOf(component) return propsArray[index] } function createElement(component, routerProps, asyncInfo) { if (component.loadProps) { return ( <AsyncPropsContainer component={component} routerProps={routerProps} asyncInfo={asyncInfo} /> ) } return React.createElement(component, routerProps) } class AsyncPropsContainer extends Component { static propTypes = { component: PropTypes.func.isRequired, routerProps: PropTypes.object.isRequired, asyncInfo: PropTypes.object.isRequired, } render() { const { component, routerProps, asyncInfo } = this.props const { propsAndComponents, loading, reload, routeChanged } = asyncInfo const asyncProps = lookupPropsForComponent(component, propsAndComponents) const props = { ...routerProps, ...asyncProps, reload, loading, routeChanged } return React.createElement(component, props) } } class AsyncProps extends Component { static propTypes = { components: React.PropTypes.array.isRequired, params: React.PropTypes.object.isRequired, location: React.PropTypes.object.isRequired, onError: React.PropTypes.func.isRequired, renderLoading: React.PropTypes.func.isRequired, renderErrorPage: React.PropTypes.func.isRequired, // server rendering propsArray: React.PropTypes.array, componentArray: React.PropTypes.array, } static defaultProps = { onError: err => { throw err }, renderLoading: () => null, renderErrorPage: () => null, } constructor(props, context) { super(props, context) const { propsArray, componentArray } = props const isServerRender = propsArray && componentArray this.state = { loading: false, prevProps: null, propsAndComponents: isServerRender ? { propsArray, componentArray } : null, routeChanged: false, routeChangeError: false, } } componentWillMount() { const { components, params, location } = this.props this.loadAsyncProps(components, params, location) } componentWillReceiveProps(nextProps) { const routeChanged = nextProps.location.key !== this.props.location.key if (routeChanged) { const { components, params, location } = nextProps this.loadAsyncProps(components, params, location) } } componentWillUnmount() { this.unmounted = true } loadAsyncProps(components, params, location) { const routeChanged = location.key !== this.props.location.key this.setState({ loading: true, prevProps: this.props, routeChanged }) const { onError } = this.props return loadAsyncProps(components, params, location). then(propsAndComponents => { const sameLocation = this.props.location.key === location.key if (sameLocation && !this.unmounted) { this.setState({ loading: false, prevProps: null, propsAndComponents, routeChangeError: false }) } }). catch(err => { const sameLocation = this.props.location.key === location.key const { routeChangeError, propsAndComponents } = this.state if (sameLocation && !this.unmounted) { this.setState({ loading: false, routeChangeError: routeChangeError || routeChanged || !propsAndComponents, }) } onError(err) }) } reload(actionName, ...args) { if (process.env.NODE_ENV !== 'production') { console.info(actionName, args) // eslint-disable-line no-console } const { components, params, location } = this.props return this.loadAsyncProps(components, params, location) } render() { const { loading, prevProps, propsAndComponents, routeChanged, routeChangeError } = this.state const { renderLoading, renderErrorPage } = this.props if (routeChangeError) { return renderErrorPage() } if (!propsAndComponents) { return renderLoading() } const asyncInfo = { loading, routeChanged, propsAndComponents, reload: ::this.reload } const props = prevProps || this.props return ( <RouterContext {...props} createElement={(component, routerProps) => createElement(component, routerProps, asyncInfo)} /> ) } } export const loadPropsOnServer = ({ components, params, location }) => loadAsyncProps(components, params, location) export default AsyncProps