reduxjs/redux

Using Redux in reusable React component

Closed this issue · 9 comments

hi,

i am working on a standalone react component, of which i would like to use multiple instances in my application. the application itself uses redux, so i thought why not also use it for the component.

the component is a filterable list of entries in a json file.

// [...]
var store = ...

// [...]
var Library = React.createClass({ ... });

var Wrapper = React.createClass({
    render: function() {
        let that = this;
        return <Provider store={store}>
            { function() { return <Library {...that.props} />; } }
        </Provider>;
    }
});

module.exports = Wrapper;

the <Library> component receives props from its parent and is also connected to the store. — so far so good.

but then i realized what having a single store means: all instances of the component would share a store.

ok, you might give each instance an id and use that in the actions / reducer to tell things apart, but that sounds like a lot of extra work. also, i don't want to make an id attribute mandatory for using the component.

isn't there a simpler way for each instance to have it's own, separate state?

The most practical suggestion I can give is to separate presentational and container components. Presentational components can be reused anywhere in the app, and you can generate container components from them using connect(). This is a good start for reusability. For example, you may have a presentational UserList component that doesn't know about Redux, and Redux-aware UserFollowersList, ArticleLikersList containers generated from it using connect(). Those containers would be bound to specific actions and listen to specific state slice.

Another approach you can take is something like https://github.com/erikras/multireducer although I don't quite like the reading complexity it seems to introduce. I'd rather pass IDs manually in the code.

Elm architecture has a nice answer to truly reusable view+update function pairs, but its downside is that you can't nest stateful components without writing plumbing code between them. Check out https://github.com/gaearon/react-elmish-example, https://github.com/ryantrinkle/reflex, https://github.com/evancz/elm-architecture-tutorial/, https://github.com/acdlite/realm if you're interested.

@gaearon Your suggestion suggests to reuse React views only. What do you think about reusing the whole composition of react view + redux reducers, actions, action creators + a store slice? For example, in a component with a complex business logic that should be in the reducers.

That's what I'm talking about in the second and third paragraphs of my previous comment.

@gaearon Thanks!

In multireducer, one has to know all the components and their identifiers at the time of combining the reducers. That's not usable if I want to dynamically create instances of these complex reusable components. Similar approach is used in redux-form, with the improvement that one should specify the identifier in the view decorator, but the implementation has its downsides, too (the component logic like validation is only customizable during reducer composition, not during definition of a component via the decorator).

The elmish example and realm look the most close to what I expect from reusability, but the examples are so small and simple, so this approach and the library can't be validated for production use (Redux is more mature on that).

Haven't done much with Realm (didn't event really mean for other people to see it yet) other than the thing I built it for, which is just one part of one app that I'm currently working on. Works really well there as a "mini-Redux" app inside a larger Redux app.

If/when I ever give it some more attention, the two things that may set it apart from similar libraries like Reflex is 1) you don't have to build your entire app out of Realm components (unless you want to) and 2) integration with the Redux ecosystem via Realm Redux.

One thing I've observed on my team is people get super excited about Redux and they start throwing everything—including component state—into what are effectively "global" reducers, which does not scale well at all beyond simple todo apps and becomes a huge pain in the ass to refactor later. I've been guilty of this as well.

Even if Realm ends up not being the answer, I am interested in this area... Redux is fantastic for application level state, and if you're not into Relay (Relay is awesome), it's pretty good at data fetching, at least relative to other Flux-ish solutions out there. However, it's not great at (nor do I think it was ever intended for) modeling component-level state.

@acdlite IMO, your team has got valuable experience for the future of frontend and Redux. I've been thinking about this global/local state compromise for a while.

What I've come up with is that sharing global state is important for:

  1. having a persistable, predictable, revertible, reproducible app state and UI state.
  2. making complex UI transitions and animations, where one component transitions into another one seamlessly, like these neat Material UI animations -- these don't look like the app-level state until you need them to sync between deeply nested components.

Everything else looks like a local state, but to have all the benefits listed above for the local state, too, I cannot put it into React element state.

Have you experienced these use cases? What have you come up with Realm or Redux or a combination of them? Have you got any examples of Redux+Realm combination?

@gaearon Sorry for hijacking your repo for this discussion, probably we should move to reactiflux discord or somewhere else.

In our applications we solved this problem in connect-like style. https://github.com/Babo-Ltd/redux-state

I'm new to redux but how about an idea:

  1. Your Library will have a static property ID that will be increased every time a new instance is created and the new instance will have its id to be the last ID value.
  2. When initializing the Library will dispatch a INIT_LIBRARY event
  3. Filtering will dispatch a FILTER_LIBRARY action with the id of the list.
  4. The Library will export a libraryReducer that will react on the INIT_LIBRARY and FILTER_LIBRARY actions. It will take the id given in the action and all the state object.
    Reacting on the INIT_LIBRARY will add to the state a porperty with the name of the id of the Library state['library' + id]. The value of the property will be an object of the items, the filter which would be empty and the filtered items which would be all the itmes at first { items: [...], filter: '', filtered: [...]}.
    Reacting on the FILTER_LIBRARY will return a new state changing only the state['library' + id] property with a new object, containing the same items, the new filter value and changing the filtered items by the filter.
  5. The initialization of the Library will require to use the exposed reducer and chain it to other reducers.
  6. The Library will subscribe to the store and will set the list of items to be the state['library' + this.id].filtered
  • Another aproach will be giving a Library component an optional name attribute. If the name is not supplied it will use the ID approach, otherwise it will use the name as a state property (possible suffixing with the library for readability)
  • The state[id] value can contain only the initial items and the filter value (without the filtered items). The filtering could be done inside the component itself. It will not heart the time travel option nor the initializing the state with a filter from cache.

So the API of the Library will expose the component as you wrote in your example and the reducer to use when bootstrapping the store, which is pretty simple to use, and hides all the complicated logic of the Library.

Hope I was clear enough and didn't miss any aspect of working with redux, again, I'm new to this (basically started learning it a few days ago)