Hunrik
Hunrik

Reputation: 483

Child route is blocking the parent route`s render

I have a react app which is using react-router. I`m using plain routes, but this is how components represent my routing

<Routes>
    <Route component={CoreLayout}>
        <Route component={AppLayout}
            onEnter={fetchData}>
            <Route path='dashboard'
                onEnter={fetchStatistics}
                component={Dashboard}>
        </Route>
    </Route>
</Routes>

The situation now

First, the app layout is going to block every render while it is fetching the necessary data from the server (like the User data). Then if we have the data, we can step on the child routes, like in this case the Dashboard route, where we are loading the content of the pages.

The goal

The problem is whit this strategy, we are going to show a blank white page until the onEnter on the main route is resolved.

To avoid this, I would like to load the AppLayout component, but without starting the onEnter function on the child route. To do this, I can show a waiting spinner where the child component would load, and when the data is loaded I can start loading the child`s data.

tl;dr

The question is, how can I make the parent layout to render, while the child route`s onEnter is not loaded.

Upvotes: 1

Views: 620

Answers (1)

Paul S
Paul S

Reputation: 36787

Instead of using onEnter, you can have your <Dashboard> initiate its data fetching in its component(Will|Did)Mount method. Have it maintain a state.loaded boolean which displays a spinner when state.loaded = false.

class Dashboard extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      loaded: false
    }
  }

  componentWillMount() {
    // mock data fetch call that uses a promise
    fetchStatistics()
      .then(resp => resp.json())
      .then(data => {
        this.setState({
          loaded: true,
          data
        })
      })
  }

  render() {
    // if data hasn't been loaded, render a spinner
    if (!this.state.loaded) {
      return <Spinner />
    }
    // data has been loaded, render the dashboard
    return (
      <div>...</div>
    )
  }
}

Edit:

It doesn't handle data loading errors, but here is an example of a general purpose data loading HOC that should work (haven't tested it):

/*
 * @Component is the component to render
 * @fetchData is a function which fetches the data. It takes
 *   a callback function to trigger once data fetching has
 *   completed.
 */
const withData = (Component, fetchData) => {
  return class WithData extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        loaded: false
      }
    }

    componentWillMount() {
      this.props.fetchData(() => {
        this.setState({ loaded: true })
      })
    }

    render() {
      return this.state.loaded ? (
        <Component {...this.props} />
      ) : (
        <Spinner />
      )
    }
  }
}

Usage

function fetchStatistics(callback) {
  fetch('/api/dashboard')
    .then(resp => resp.json())
    .then(data => {
      dispatch(dashboardData(data))
      callback()
    })
})

<Route
  path='dashboard'
  component={withData(Dashboard, fetchStatistics} />

Upvotes: 1

Related Questions