New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component Loading #1390

Closed
bradwestfall opened this Issue Feb 11, 2016 · 2 comments

Comments

2 participants
@bradwestfall

I'm curious about best practices for my use-case (which I believe is probably very common). It's combination of react, redux, react-router but I believe the discussion is best suited for redux

Documentation and guides are very vague when it comes to component-to-component communication (of a non parent-child nature). Imagine using react-router where the overall page is derived of nested layout components such as

ReactDOM.render((
    <Router>
        <Route component={DefaultLayout}>
            <Route path="foo" component={FooLayout}>
                <Route path="products" component={Products}>
                <Route path="users" component={Users}>
                <Route path="widgets" component={Widgets}>
            </Route>
        </Route>
    </Router>
), document.getElementById('app'))

DefaultLayout looks like this:

export default React.createClass({
    render: function() {
        return (
            <div className="default-layout">
                <div class="receive-any-component"></div>
                <main>
                    {this.props.children}
                </main>
            </div>
        )
    }
})

For the sake of this example, FooLayout (loaded into this.props.children) can be any sub layout you want to imagine.

All three routes: /products, /users, or /widgets have a button in their component. When clicked, it will load a whole new component into the DefaultLayout's <div class="receive-any-component"></div> section. For example

Products component:

import store from 'path/to/store'

export default React.createClass({
    render: function() {
        return (
            <div className="product">
                Lorem ipsum...
                <button onClick={this.onClick}>Click Me</button>
            </div>
        )
    },
    onClick: function() {
        // There are several ways to have a component loaded DefaultLayout's
        // div.receive-any-component
    }
})

This whole example is over simplified. I'm using react-redux in the real thing. Official react docs only say one small bit on this type of use-case:

For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event. Flux pattern is one of the possible ways to arrange this.

So, I understand that I can use Redux to dispatch info whereby DefaultLayout is subscribed, but I need to load a whole component into DefaultLayout. It would seem to me there are two basic options, neither of which I feel are correct.

Option One

Have our Product component dispatch the component we want to load:

import store from 'path/to/store'
const componentToLoad = (
    <Whatever />
)

export default React.createClass({
    render: function() {
        return (
            <div className="product">
                Lorem ipsum...
                <button onClick={this.onClick}>Click Me</button>
            </div>
        )
    },
    onClick: function() {
        store.dispatch({
            type: 'LOAD',
            component: componentToLoad
        })
    }
})

Ultimately, I'd like the DefaultLayout to be unaware of what components it's loading, so this solution works out in that regards. But sending a component into redux just seems so wrong, but it works.

Option Two

Instead of dispatching a component, dispatch some state that indicates which component should be loaded. In this case, it would seem that DefaultLayout would have to import every possible component it might need to load (which in my real usecase is many), then subscribe to our state and use a switch statement to determine which one of our loaded components should be placed into .receive-any-component. This feels better from the redux end but feels crappy that DefaultLayout is aware of every possible component that it might need to load.

I'm not new to JS, but somewhat new to React/Redux. What am I missing?

And I apologize this question is so long. It's difficult for me to describe this any other way

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Feb 11, 2016

Collaborator

It is advisable that both actions and state are always serializable. Therefore putting a component into the action doesn’t seem like the correct way, and I would encourage you not to do that.

Instead of dispatching a component, dispatch some state that indicates which component should be loaded. In this case, it would seem that DefaultLayout would have to import every possible component it might need to load (which in my real usecase is many), then subscribe to our state and use a switch statement to determine which one of our loaded components should be placed into .receive-any-component. This feels better from the redux end but feels crappy that DefaultLayout is aware of every possible component that it might need to load.

This is exactly what I would suggest. I don’t think at all it is bad. You can have a separate module that does this.

import Button from './Button'
import Checkbox from './Checkbox'

function resolveComponent(componentId) {
  switch (componentId) {
  case 'BUTTON':
    return Button
  case 'CHECKBOX':
    return Checkbox
  }
}

and in your component:

function mapStateToProps(state) {
  let Type = resolveComponent(state.componentId)
  let children = <Type />
  return { children }
}

If your bundler supports code splitting, you can even make resolveComponent dynamically load components and return a promise so you can show some default view while component is being loaded.

Collaborator

gaearon commented Feb 11, 2016

It is advisable that both actions and state are always serializable. Therefore putting a component into the action doesn’t seem like the correct way, and I would encourage you not to do that.

Instead of dispatching a component, dispatch some state that indicates which component should be loaded. In this case, it would seem that DefaultLayout would have to import every possible component it might need to load (which in my real usecase is many), then subscribe to our state and use a switch statement to determine which one of our loaded components should be placed into .receive-any-component. This feels better from the redux end but feels crappy that DefaultLayout is aware of every possible component that it might need to load.

This is exactly what I would suggest. I don’t think at all it is bad. You can have a separate module that does this.

import Button from './Button'
import Checkbox from './Checkbox'

function resolveComponent(componentId) {
  switch (componentId) {
  case 'BUTTON':
    return Button
  case 'CHECKBOX':
    return Checkbox
  }
}

and in your component:

function mapStateToProps(state) {
  let Type = resolveComponent(state.componentId)
  let children = <Type />
  return { children }
}

If your bundler supports code splitting, you can even make resolveComponent dynamically load components and return a promise so you can show some default view while component is being loaded.

@gaearon gaearon closed this Feb 11, 2016

@gaearon gaearon added the question label Feb 11, 2016

@bradwestfall

This comment has been minimized.

Show comment
Hide comment
@bradwestfall

bradwestfall Feb 11, 2016

Thanks, I can massage this into my real example

Thanks, I can massage this into my real example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment