makeomatic / redux-connect

Provides decorator for resolving async props in react-router, extremely useful for handling server-side rendering in React
MIT License
549 stars 67 forks source link

HOC with wrapped component with asyncConnect #116

Open ronar opened 7 years ago

ronar commented 7 years ago

Hi! Is it possible to make HOC which wraps asyncConnect'ed component. I can't do this. Because all asyncItems, mapStateToProps and mapDispatchToProps of the Component seem to be dropped.

Say, I've got the Component:

...
const mapStateToProps = state => ({
    ...
});

const asyncItems = [{
    key: 'myItems',
    promise: ({router, store: { dispatch, getState }}) => {}
}];
const enhancer = createHOC();

module.exports = enhancer(asyncConnect(
    asyncItems,
    mapStateToProps,
    mapDispatchToProps,
)(Component));

And HOC itself has the asyncConnect enhancer :) Is it possible or do I have to abandon this idea at all?

AVVS commented 7 years ago

move static over to the enhancer in HOC, that's really the only thing you can do here to get this to work. Generally asyncConnect must be the top HOC

ronar commented 7 years ago

Okay, it make sense. And how to correctly implement HOC, make a factory which receives asyncItems, mapStateToProps and dispatchProps and returns parametrised enhancer with asyncConnect?

const enhancerFactory = (asyncItems = [], mapStateToProps, dispatchProps) => {
    ...

    function wrapComponent(wrappedComponent) {
        ...
        const enhancer = compose(
            asyncConnect(asyncItems, finalMapStateToProps, dispatchProps)
        );

        return enhancer(hoistNonReactStatic(HOCComponent, WrappedComponent));
    }

    return wrapComponent;
}
gerhardsletten commented 6 years ago

Had a issue with loadable-components which also needed to be a direct decender of the Route, but then I could use the getComponent in react-router v3 to do the loading, and then send the redux-connect HOC to react-router

https://github.com/smooth-code/loadable-components/issues/15

internetErik commented 6 years ago

@gerhardsletten Any chance you've also been able to get redux-connect and loadable-components to work with react-router v4? Right now I can't see how since async-connect and loadable-components both seem to need to be the top level HOC.

I wouldn't mind trying my hand a pull request to add features to make this possible in redux-connect, but I'd feel better in talking about it with somebody first to plan out what needs to be done. Maybe @AVVS could help provide some direction for me (thanks for this component by the way - it's been very handy)?

I think #118 is related to this, too.

gerhardsletten commented 6 years ago

@internetErik No, have not had the time to upgrade for RR4 after redux-connect supports it. But did you see my updated comment here about the need for a static componentId property on the component to ensure that loadable-components can register it?

Also take a look at this article by the airbnb about adding a function for the component in the config. You should then be able to itterate matched routes, and prepare them before you give them to redux-connects loadOnServer function.

Maybe there should be made an example for this. Async loading both data and components is what you expect of an application today

internetErik commented 6 years ago

@gerhardsletten Thanks for the info! I think I may be able to figure something out from that. (The link to the airbnb article seems to be broken btw.)

gerhardsletten commented 6 years ago

@internetErik updated the link now in the comment below!

gerhardsletten commented 6 years ago

After upgrading to RR v4 and the latest version of redux-connect, I think that the solution for both data-fetching and code-splitting is about organizing you code. The route-level component can't both be HOC'ed with redex-connect and be lazy-loaded, so you should do data-fetching in the route-level component and move lazy-loading into a child-component. Can't think of any other way to do this, since you don't have a getComponent function with react-router-config, without writing you own helper that both lazy-load and do data-fetching. Maybe this should be mentions in the README.md to save people from spending time looking into this?

internetErik commented 6 years ago

@gerhardsletten Do you have any example of this you could share by any chance? It would be helpful to me to see some of what you're doing in particular.

gerhardsletten commented 6 years ago

Hi, this is an example of a controller. You just move the visuals and heavy dependencies into its own component, and just hot-load it from the controller, but keep the asyncConnect hoc on the controller so redux-connect will discover it through react-router-config

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { asyncConnect } from 'redux-connect'
import loadable from 'loadable-components'

import { isLoaded as isPageLoaded, load as loadPage } from 'redux/modules/page'

const Page = loadable(() =>
  import(/* webpackChunkName: "page" */'./Page')
)

@asyncConnect([
  {
    promise: ({ store: { dispatch, getState }, location: { pathname } }) => {
      const promises = []
      if (!isPageLoaded(getState(), pathname)) {
        promises.push(dispatch(loadPage(pathname)))
      }
      return Promise.all(promises)
    }
  }
])
@connect(state => ({
  page: state.page.data,
  error: state.page.error
}))
export default class PageContainer extends Component {
  render() {
    return <Page {...this.props} />
  }
}
internetErik commented 6 years ago

@gerhardsletten Thanks, this is really helpful to see!