ctrlplusb / react-async-component

Resolve components asynchronously, with support for code splitting and advanced server side rendering use cases.
MIT License
1.45k stars 62 forks source link

Component props in resolve function #51

Closed sergei-zelinsky closed 6 years ago

sergei-zelinsky commented 6 years ago

Hi, there.

I have next usage question.

Before import module I need to fetch API service to resolve what module I have to import. My application uses SEO-friendly routing. For example:

So in resolve function of my async component I need to get current location from props (I use react-router v4).

All my routing consists of the next line:

<Route path="*" component={AsyncComponentResolver}/>

I didn't find any information how to use props in resolve function and write wrapper component:

class AsyncComponentResolver extends Component {
  render(){
    const {pathname} = this.props.location;

    const AsyncComponent = asyncComponent({
      resolve: () => {
        return API.fetchRouteInformation(pathname)
          .then(({modulePath}) => import(modulePath))
      },
      LoadingComponent: ({ match }) => <div>Resolving {match.url}</div>
    });
    return <AsyncComponent {...this.props}/>
  }
}

But this solution doesn't work in stage of server-side rendering and I have <div>Resolving /my-very-nice-article</div> in rendered HTML instead of rendered ArticlePage component.

Thanks! It will be very nice if you show me the right way.

sergei-zelinsky commented 6 years ago

@ctrlplusb could you help me, please.

ctrlplusb commented 6 years ago

Hey @sergei-zelinsky unfortunately you will always have problems when combining dynamically created asyncComponent instances with server side rendering. This is because internally I have to manage a unique identifier that gets attached to each created asyncComponent instance so that on the render parse I know which components to rehydrate etc. Sorry 😢

@faceyspacey may have some helpful insight into this for you. He also has an alternative solution with a massive focus on SSR experience.

faceyspacey commented 6 years ago

@sergei-zelinsky funny thing is I'm releasing the precise answer to universal "dynamic" imports as we speak. Just this morning Webpack merged my PR (https://github.com/webpack/webpack/pull/5235#issuecomment-317897559) to make it possible for the first time.

So import(`./${page`) has worked, but to bring everything together you need to use require.resolveWeak(`./${page`) to synchronously render on the server and on first load on the client.

However that didn't work until the PR. Only static resolves like this worked: require.resolveWeak('./Foo').

So the dream of code-splitting has revolved around large hashes of all the potential components you might want to render. My thinking has been that once it's frictionless, simultaneous code-splitting + SSR will reach its inflection point and finally become accessible.

I'll respond with a solution to the "universal" aspects tomorrow, but the short of it is you should be able to do this:

const asyncComponents = {
  ArticlePage: () => import('./ArticlePage'), // make sure the imports are guarded by functions
  ArticleStatisticsPage: () => import('./ArticleStatisticsPage'), // so they aren't called until you need them 
}
// ...
resolve: () => {
  return API.fetchRouteInformation(pathname)
    .then(({ modulePath }) => {
         return asyncComponents[modulePath]().default
    })
},

regarding SSR: This package does an excellent job avoiding checksum mismatches on the client along with SSR, but it requires making additional async imports after the page loads. So it doesn't support synchronous rendering on the server or on initial load on the client. I'll get back to you tomorrow after I release the latest on how to do that. It's been a long road to make this frictionless. And the problem you've run into--though not the hardest to solve with existing solutions--has been a point of confusion for many.

ps. @ctrlplusb i checked your re-implementation of Apollo's recursive promise resolution on the server a while back, and was really inspired by it. Too little time, but there's a lot we can do with that to combine universal rendering with universal component-level data-fetching. I actually wrote a 1st draft a month ago using the bootstrapper package you made. I just need to re-incorporate it with the latest React Universal Component stuff revolving around the above PR.

ctrlplusb commented 6 years ago

@faceyspacey sounds like some exciting work dude! defo keep me posted. :)

faceyspacey commented 6 years ago

@ctrlplusb thanks!

...and actually my bad, as i said, dynamic imports work for SPAs (just not for universal apps using require.resolveWeak). So you can simplify the above hash-based approach to having a single import():

const asyncComponents = {
  'my-very-nice-article': 'ArticlePage',  // these are strings
  'statistics-for-my-other-very-nice-article': 'ArticleStatisticsPage', // not the component itself
}

const getAsyncComponent = ({ modulePath }) => import(`./${asyncComponents[modulePath]}`)
   .then({ default: Component }) => Component)

// ...

resolve: () => {
  return API.fetchRouteInformation(pathname).then(getAsyncComponent)
},
faceyspacey commented 6 years ago

@sergei-zelinsky hey brother never heard back from you. In any case, here's the article which has the rest of the answers to your questions regarding universally rendering:

https://medium.com/faceyspacey/announcing-react-universal-component-2-0-babel-plugin-universal-import-5702d59ec1f4

sergei-zelinsky commented 6 years ago

Hi guys. Sorry for the late response, it was difficult to answer earlier. You really helped me. Thanks a lot for your contribution into open-source community and best support. Great work! @ctrlplusb @faceyspacey