reduxjs / react-redux

Official React bindings for Redux
https://react-redux.js.org
MIT License
23.37k stars 3.37k forks source link

Mounting container to any state's property #575

Closed verkholantsev closed 7 years ago

verkholantsev commented 7 years ago

Hi,

I've got a container that is mapped to some state:

const Container = connect(state => ({prop: state.someProp}))(Component);

And I've got some state that is defined by combineReducers structure:

const reducer = combineReducers({
  propA: someReducer,
  propB: someReducer,
});

I want to mount two instances of Container, that are mapped to two different properties of state (propA and propB) without rewriting Container.

After some experiments I and my colleague come to some implementation like this:

const ContainerA = MountComponentTo(Container, 'propA');
const ContainerB = MountComponentTo(Container, 'propB');

const App = () => (
  <Provider store={store}>
    <ContainerA/>
    <ContainerB/>
  <Provider>
);

function MountComponentTo(Component, propName) {
  class MountedComponent extends React.Component {
    constructor(props, context) {
      super(props, context);
      this.store = context.store;
    }

    getChildContext() {
      let store = this.store;
      const getState = this.store.getState.bind(this.store);

      store = Object.assign({}, store, {
        getState: () => getState()[propName]
      });

      return {store};
    }

    render() {
      return Children.only(this.props.children);
    }
  }

  MountedComponent.contextTypes = {
    store: React.PropTypes.object
  };

  MountedComponent.childContextTypes = {
    store: React.PropTypes.object
  };

  return MountedComponent;
}

Is it a proper implementation? Is there any better way to do this with react-redux?

markerikson commented 7 years ago

Yeah, the "multiple independent instances of a connected component" scenario can get a bit complex. There's been a lot of discussion on the topic, and there's a bunch of different libraries people have built to try to help with it. Let me point you to some references.

I have a bunch of articles on Redux and encapsulation in my links list, at https://github.com/markerikson/react-redux-links/blob/master/redux-architecture.md#encapsulation-and-reusability . In particular, there's a repo with discussions on various solutions to the "arbitrary list of counters" sample problem at https://github.com/slorber/scalable-frontend-with-elm-or-redux . Also, my Redux addons catalog has a page listing various "per-component state"/"encapsulation"-type libraries at https://github.com/markerikson/redux-ecosystem-links/blob/master/component-state.md .

I haven't specifically used any of the libraries in that list myself, but just to pick three that seem interesting-ish, you might want to look at https://github.com/threepointone/redux-react-local , https://github.com/epeli/lean-redux , and https://github.com/eloytoro/react-redux-uuid .

jimbolla commented 7 years ago

I'm not a fan of shadowing the store to only expose a fragment of the store. I feel like that gets you in trouble as soon as Container starts using a component that also connects to the store but expects the full store state. I'd prefer to do something like:

const App = () => (
  <Provider store={store}>
    <Container propName="propA" />
    <Container propName="propB"/>
  <Provider>
);

and then make Container's mapStateToProps use propName to pluck from state.

verkholantsev commented 7 years ago

@jimbolla If you have some complex containers, that connect some components on different levels of result tree, the scenario with <Container propName="propB"/> suggests passing propName from outer component to inner component. Provider component is really great thing because it passes store in tree context. I think that passing propName is a step back in this circumstances.

@markerikson Isn't it a good decision to add some component to react-redux (just like Provider) that makes this context and store manipulations?

markerikson commented 7 years ago

@verkholantsev : there's too many different potential use cases out there to try to implement things here in react-redux. That's part of why there's so many different third-party libraries. There was actually a good article just today on a related topic, entitled "Libraries shouldn't support everything".

That's not to say that it's impossible to implement some kind of "isolation" approach in react-redux, or that it's an "absolute never" that such a thing would be implemented, but I definitely don't see us adding anything like that in the near future.