简体   繁体   中英

NextJS official sample code: _app.getInitialProps calls Component.getInitialProps — is this to make sure all pages load data?

I'm trying to understand how to connect redux-saga to NextJS and am following the sample code they provide -- https://github.com/zeit/next.js . I understand that one can load data from within getInitialProps but I don't understand what is going on with the call to Component.getInitialProps :

class MyApp extends App {
  static async getInitialProps({Component, ctx}) {
    let pageProps = {}
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps({ctx})
    }

    return {pageProps}
  }

  render() {
    const {Component, pageProps, store} = this.props
    return (
      <Container>
        <Provider store={store}>
          <Component {...pageProps} />
        </Provider>
      </Container>
    )
  }
}

export default withRedux(createStore)(withReduxSaga({async: true})(MyApp))

Is this allowing all async loads within the getIntialProps of pages to load? That is, in index.js we have the code

class Index extends React.Component {
  static async getInitialProps (props) {
    const { store, isServer } = props.ctx
    store.dispatch(tickClock(isServer))

    if (!store.getState().placeholderData) {
      store.dispatch(loadData())
    }

    return { isServer }
  }

  componentDidMount () {
    this.props.dispatch(startClock())
  }

  render () {
    return <Page title='Index Page' linkTo='/other' NavigateTo='Other Page' />
  }
}

Will this getInitialProps wait to return until all the data is loaded? How will it know when it's loaded?

Any help much appreciated!

  1. Since _app.js is a HoC your Component inside the _app.js is basically the page you want to load from the pages folder (if you compose further you can propagate another HoC and afterwards load your page, depends on your application, but then in your compose HoC you have to implement getInitialProps again and then execute the getInitialProps of your final page.js). Each page can have an own getInitialProps (eg. on your contact page you want to load your companies address and only there in your entire application). _app.js executes Component.getInitProps (if set). If the Component's method returns a non-empty object, it becomes your pageProps which you finally provide to the Component inside the render method.

  2. Your page.js now has a getInitProps method implemented, which dispatches a saga task and returns isServer ... that given, only your store is hydrated by dispatching the action - the getInitProps does not have to wait for anything and returns a boolean. After the store hydration your component will be updated, since your props in store are loaded/updated.

However I face the same issue at the moment:

I dispatch saga Tasks in my getInitProps, but since the actions are async I receive the props after ComponentDidMount.

EDIT:

Link to read

TL:DR;

  • Saga tasks are threads or if you want a kind of event listeners.
  • Everytime you dispatch a saga action (depending on your sagas) a listener is spinned up
  • In our case if we fire an action in our getInitialProps we dispatch actions to the store using saga tasks, however the store.dispatch method is not async => which leads to getInitialProps is not blocked by the action and instantly returns props although the fired tasks aren't finished
  • Victor's solution as well as the one from next-redux-saga package is 1. to dispatch the 'END' action which stops the current root task and 2. apply the toPromise() method which gives you the ability to asynchronously wait for the sagas to end. => You kind of block the getInitialProps and force the method to wait for the sagas to end before the method return;
  • Important: when you stop your saga threads it is okay and MUST/SHOULD be done on the server, but your application needs the sagas on the client side to properly handle store side effects thus you MUST rerun the sagas if !isServer.

UPDATE:

It doesnt work. You can only stop the sagas on the server because you only need them for the initial execution, the sagas on the server are pretty useless afterwards. However on the client side you cannot stop the sagas.

Here is my store.js... you can execute initial sagas and the server will wait for them to finish in getInitialProps, since the toPromise() makes the stopping async.

On the client side you have to work your way through lifecycles etc... client side store hydration does not work as I expected it to work. Maybe it is better since otherwise you would block React from rendering, which goes against React in general.

  store.runSagas = () => {
    if (!store.currentSagas) {
      store.currentSagas = sagaMiddleware.run(rootSaga);
    }
  };

  store.stopSagas = async () => {
    if (store.currentSagas) {
      store.dispatch(END);
      await store.currentSagas.toPromise();
    }
  };

  store.execTasks = isServer => async (tasks = []) => {
    tasks.forEach(store.dispatch);
    if (isServer) {
      await store.stopSagas();
    }
  };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM