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

Using Redux in reusable React component #1098

Closed
freder opened this Issue Dec 4, 2015 · 9 comments

Comments

6 participants
@freder

freder commented Dec 4, 2015

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?

@gaearon gaearon added the discussion label Dec 4, 2015

@gaearon gaearon changed the title from using redux in reusable react component to Using redux in reusable react component Dec 4, 2015

@gaearon gaearon changed the title from Using redux in reusable react component to Using Redux in reusable react component Dec 4, 2015

@gaearon gaearon changed the title from Using Redux in reusable react component to Using Redux in reusable React component Dec 4, 2015

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Dec 4, 2015

Collaborator

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.

Collaborator

gaearon commented Dec 4, 2015

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 gaearon closed this Dec 4, 2015

@sompylasar

This comment has been minimized.

Show comment
Hide comment
@sompylasar

sompylasar Dec 4, 2015

@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.

@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.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Dec 4, 2015

Collaborator

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

Collaborator

gaearon commented Dec 4, 2015

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

@sompylasar

This comment has been minimized.

Show comment
Hide comment
@sompylasar

sompylasar Dec 4, 2015

@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).

@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).

@acdlite

This comment has been minimized.

Show comment
Hide comment
@acdlite

acdlite Dec 4, 2015

Collaborator

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.

Collaborator

acdlite commented Dec 4, 2015

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.

@sompylasar

This comment has been minimized.

Show comment
Hide comment
@sompylasar

sompylasar Dec 4, 2015

@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.

@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.

@galkinrost

This comment has been minimized.

Show comment
Hide comment
@galkinrost

galkinrost Feb 14, 2016

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

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

@slavik57

This comment has been minimized.

Show comment
Hide comment
@slavik57

slavik57 Dec 1, 2016

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)

slavik57 commented Dec 1, 2016

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)

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