7

I'm trying to achieve functionality, where each Route will first wait for some ajax promise to resolve, and only then will render the route. I saw that onEnter doesn't exist anymore, so i'm trying the render method.

My routes are defined like that:

{cmsRoutes.map((route, idx) => {
              console.log(route.resolve)
              return route.component ? (<Route  key={idx} path={route.path} exact={route.exact} name={route.name} render={props =>{
               route.resolve()
               .then(({data})=>{
                 console.log(data)
                  return (
                <route.component {...props} />
              )
                }) 

              } } />)
                : (null);
            },
            )}

As you can see, it just iterates over some array, that holds the data for each route. One of the fields of the route object is "resolve", which points to a function, that returns a promise. Like this one:

const resolvemanageContactApplications = ()=> {
  return ajax('/contact')
};

When executing this, i get the following error:

Route(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.

This of course happens, because while the ajax is being executed, nothing is returned from the route. The question: how can i make React router 4 wait for the promise resolution? There has to be some way. I remember that AngaulrJS UI-Router actually had some "resolve" api.

i.brod
  • 2,609
  • 4
  • 18
  • 45

2 Answers2

4

You need another approach here.

You should use a state to store if your ajax is done or not. Then, to render the route you can render nothing (null or ...loading) if the ajax isn't done, and render the actual component or whatever you want with the result of the ajax.

I can see you are generating the routes based on an array of data, and to achieve that I would create a Component to wrap the data-loading condition. It could receive as props:

  • The resolve (the ajax function)
  • The component to render

On the componentDidMount and componentDidUpdate do the ajax call and set the state.waitingResolve to true, and on the .then() of the resolve, set the state.waitingResolve to false, and store the returned data to the state too.

On the render, you check if state.waitingResolve, and renders null (or ...loading) or the component to render that you received from props.

It may look like complicated but it is worth.

Murilo Cruz
  • 1,819
  • 13
  • 18
4

You can create some async component wrapper like:

class AsyncComponent extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      Component: null
    };
  }

  componentDidMount() {
    const { componentPromise } = this.props;
    componentPromise.then(component => this.setState({
      Component: component
    }));
  }

  render() {
    const { Component } = this.state;

    if (!Component) {
      return null; // You can return some spinner here
    }

    return (
      <Component {...this.props} />
    );
  }
}

And use it inside of your router:

<Route
  key={idx}
  path={route.path}
  exact={route.exact}
  name={route.name}
  render={props => (
    <AsyncComponent componentPromise={route.resolve} {...props} />
  )}
/>
Yuriy Yakym
  • 2,339
  • 12
  • 24
  • I see. So i basically need to move this logic out of the router – i.brod Jun 19 '18 at 16:01
  • 2
    So i've tested it, with slight adjustments, and it works, but not in the way i would like it to. What happens is, that my previous component unmounts and disappears form the screen, instead of which nothing appears, until the promise is resolved. This is not the UX i'm trying to achieve. What i need, is for the original screen to stay present, until the promise is resolved. Imagine it like good old server side rendering: Only when the http request has returned- the original page is switched with the new one. This is why i tried to do it from the router – i.brod Jun 19 '18 at 17:23
  • 1
    There is a lot o discussion about this: https://stackoverflow.com/questions/37849933/react-router-how-to-wait-for-an-async-action-before-route-transition . I couldn't do it with the onEnter event, but had a better chance dealing directly with the history event handlers. But I don't recommend you to implement a UX like that, as it reduces responsiveness. Maybe you want to fire a async action (like in a button) that, when it finishes, it calls history.push to do the route change after the data is already loaded – Murilo Cruz Jun 20 '18 at 11:44