简体   繁体   中英

Next.js keeps current page active and re-rendering while getInitialProps of next page finishes loading

The scenario is something like this:

  1. In one page ( AssessmentListPage ) I'm showing a list of existent assessments; In a second page ( AssessmentDetailPage ) I'm showing the details of the selected assessment;
  2. I'm using redux and both pages are using the same slice os state ( assessments )
  3. When I'm moving from AssessmentListPage to AssessmentDetailPage , everything works normally; When I hit the back button it crashes.
  4. The reason it crashes : Next.js keeps the AssessmentDetailPage page active until the static getInitialProps method finishes. The actions being called change the state and cause the current page to re-render without the expected data. And then it render the AssessmentListPage correctly.

What would be the most elegant way to deal with this? Should I separate the reducers?

Currently I'm just being more defensive and adding checks that I'd normally won't need to avoid this specific crash.

I also thought about not loading the data in the getInitialProps and doing at componentDidMount() but I don't like the idea of duplicating the code to handle both server side and client side rendering.

Any advice is much appreciated. Thanks!

class AssessmentListPage extends React.Component {
  static async getInitialProps({ store, req }) {
    await store.dispatch(loadProfile(api)(req))
    await store.dispatch(loadAssessments(api)(req))
  }
}

class AssessmentDetailPage extends React.Component {
  static async getInitialProps({ store, req }) {
    await store.dispatch(loadProfile(api)(req))
    await store.dispatch(loadAssessment(api)(req, query.id))
  }
}

If you are using Redux to synchronize the state between several components for one of the reasons exposed here: https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367 , it is ok. But if it is only because you want to reuse the loading part, then 'an elegant way' should be using a local state for each page, and putting all loading code in a HOC:

// withAssessmentInfo.js
export default (WrappedComponent) => {
  return class AssessmentInfo extends React.Component {
     static async getInitialProps (context) {
        const props = await super.getInitialProps(context)
        props.profile = await loadProfile(api)(context.req)
        props.assessment = await loadAssessment(api)(context.req, context.req.query.id)
   }

   return {
    ...(WrappedComponent.getInitialProps ? await WrappedComponent.getInitialProps(context) : {}),
    ...props
   }
 }

 render () {
   return (
    <WrappedComponent {...this.props} />
   )
 }
}}

So now, you can load the data in this way:

class AssessmentListPage extends React.PureComponent {
   static propTypes = {
     profile: PropTypes.object.isRequired,
     assessment: PropTypes.array.isRequired
   }

   static async getInitialProps(context) {
      // load specific data for this page here
   }

   render () {
     const { profile, assessment } = this.props

     if (profile && assessment) {
       <div>your component code</div>
     } else {
       return (
         <div>Loading some data...</div>
       )
     }
   }
}

export default withAssessmentInfo(AssessmentListPage)

The same idea for AssessmentDetailPage.

References:

https://reactjs.org/docs/higher-order-components.html https://medium.com/@Farzad_YZ/handle-loadings-in-react-by-using-higher-order-components-2ee8de9c3deb https://medium.com/@mcculloughjchris/fetching-data-with-a-higher-order-component-in-react-43c622c30a82 https://medium.com/@guigonc/refactoring-moving-api-calls-to-a-higher-order-component-53062c086cb

But if you need or you want to keep the Redux idea, then if detail page and list page are sharing profile and assessment props from the Redux State, so you should add to the Redux State and indicator so you can coordinate all data loaded:

// Redux State
{
  assessmentData: {
     operation: 'ready', // ready || loading
     profile: null,
     assessments: null
  }
}

And if you always load profile and assessment together, you can use an async action to load both of them ( https://github.com/reduxjs/redux-thunk ) or using a middleware:

export default store => next => action => {
    const result = next(action)

    if (action.type === LOAD_ASSESSMENT_INFO) {
       store.dispatch({
         type: LOADING_ASSESSMENT_INFO // the action put the loading state
       })
       const profile = await loadProfile(api)
       const assessment = await loadAssessment(api)(action.payload.id)

       store.dispatch({
         type: LOADED_ASSESSMENT_INFO, // this action put the ready state, and set the data
         payload: {
           profile,
           assessment
         }
       })
    }

    return result
}

Even so, you should read this link How to to cancel pending asynchronous actions in React/Redux , and you should prevent to keep loading when an async action should be canceled.

I hope this information helps you to find the best way to structure your project to deal with loading data in using NextJS.

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