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

Investigate using Redux for pseudo-local component state #159

Closed
gaearon opened this Issue Jun 21, 2015 · 54 comments

Comments

@gaearon
Collaborator

gaearon commented Jun 21, 2015

As @slorber notes, once you go single-treeish, local component state begins to bother you.

I don't feel strongly about it (I'm fine with some local component state here and there), but it would be interested to explore API and implementation-wise the idea of replacing React local component state with state backed by Redux with “ephemeral” reducers whose data is erased when their owner component unmounts.

I'm not sure if I'm actually making sense here.. Let's say that the litmus test is:

  • Have some API that looks similar to React's local state API. For example, a container component that injects state and setState as props.
  • Unlike React's local state API, under the hood, it works by emitting actions like { type: SET_LOCAL_STATE, ownerComponentId: '42424242' }
  • There is a reducer that handles these actions. The container component subscribes to the slice of the state governed by that reducer
  • (The nice part) By default component IDs are unique but you can specify a function that generates the key from props. This lets components “mirror” the same state or “transplant” it across the tree. (cc @chenglou)

Related: rethinking React's key and state.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Jun 21, 2015

Collaborator

Note that I think all of this can be implemented totally independent from Redux in a separate repo. Anybody wanna try?

Collaborator

gaearon commented Jun 21, 2015

Note that I think all of this can be implemented totally independent from Redux in a separate repo. Anybody wanna try?

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Jun 21, 2015

Collaborator

The tricky part of course is to figure out when (and whether) to erase such state. This may be a bad idea after all, see @jimfb's comments in this gist. But I still think we ought to give it a try..

Collaborator

gaearon commented Jun 21, 2015

The tricky part of course is to figure out when (and whether) to erase such state. This may be a bad idea after all, see @jimfb's comments in this gist. But I still think we ought to give it a try..

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Jun 21, 2015

Collaborator

I think a great first approximation is to make it work so that the “pseudo-local” state is always destroyed when the component unmount. The set of “actions” would be MOUNT_LOCAL_STATE, SET_LOCAL_STATE, UNMOUNT_LOCAL_STATE. This is already cool because this enables time travel and logging at Redux level even for “local” state.

Collaborator

gaearon commented Jun 21, 2015

I think a great first approximation is to make it work so that the “pseudo-local” state is always destroyed when the component unmount. The set of “actions” would be MOUNT_LOCAL_STATE, SET_LOCAL_STATE, UNMOUNT_LOCAL_STATE. This is already cool because this enables time travel and logging at Redux level even for “local” state.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Jun 21, 2015

Collaborator

Here's something interesting. This is totally implementable outside core as long as the user doesn't forget to include our “local state reducer” in their global reducer. Doesn't this sound similar to what I wanted in #113? It's the same “third party” state and “third party” actions all over again. It sounds like there's a special sort of functionality many plugins want to have: the ability to write to a custom slice of sub-atom that user is not managing explicitly. @acdlite

Collaborator

gaearon commented Jun 21, 2015

Here's something interesting. This is totally implementable outside core as long as the user doesn't forget to include our “local state reducer” in their global reducer. Doesn't this sound similar to what I wanted in #113? It's the same “third party” state and “third party” actions all over again. It sounds like there's a special sort of functionality many plugins want to have: the ability to write to a custom slice of sub-atom that user is not managing explicitly. @acdlite

@dariocravero

This comment has been minimized.

Show comment
Hide comment
@dariocravero

dariocravero Jun 21, 2015

Contributor

@gaearon we're almost doing that in panels (a runtime to run independent apps with a very particular (mobile focused) UX). However, because each app is meant to be independent, we isolate its flux instance in what we call contexts which is part of the runtime's atom. I should be in a position to show you what I just described in real code sometime around the conference if you're interested. I'm interested to see how this unfolds and how I can help shape it. :)

Contributor

dariocravero commented Jun 21, 2015

@gaearon we're almost doing that in panels (a runtime to run independent apps with a very particular (mobile focused) UX). However, because each app is meant to be independent, we isolate its flux instance in what we call contexts which is part of the runtime's atom. I should be in a position to show you what I just described in real code sometime around the conference if you're interested. I'm interested to see how this unfolds and how I can help shape it. :)

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Jun 21, 2015

Collaborator

Cool, see you at the conf then!

Collaborator

gaearon commented Jun 21, 2015

Cool, see you at the conf then!

@dariocravero

This comment has been minimized.

Show comment
Hide comment
@dariocravero

dariocravero Jun 21, 2015

Contributor

Definitely :)

Contributor

dariocravero commented Jun 21, 2015

Definitely :)

@matystl

This comment has been minimized.

Show comment
Hide comment
@steida

This comment has been minimized.

Show comment
Hide comment
@steida

steida Jun 22, 2015

After actions as pure functions, initial state, state-less flux, another stuff I'm using in http://github.com/steida/este Do we follow the same logic steps?

I'm calling that pattern globalized local state: https://github.com/steida/este/blob/13c59be76c39ecfabbf6f8ada17e8ffde0e59cbf/src/client/components/editable.react.js#L101

steida commented Jun 22, 2015

After actions as pure functions, initial state, state-less flux, another stuff I'm using in http://github.com/steida/este Do we follow the same logic steps?

I'm calling that pattern globalized local state: https://github.com/steida/este/blob/13c59be76c39ecfabbf6f8ada17e8ffde0e59cbf/src/client/components/editable.react.js#L101

@iammerrick

This comment has been minimized.

Show comment
Hide comment
@iammerrick

iammerrick Jul 26, 2015

I don't know that it makes sense to couple transient state with the component. Maybe I'm misunderstanding but really I want transient state that potentially spans multiple components....

I don't know that it makes sense to couple transient state with the component. Maybe I'm misunderstanding but really I want transient state that potentially spans multiple components....

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Jul 26, 2015

Collaborator

I don't know that it makes sense to couple transient state with the component. Maybe I'm misunderstanding but really I want transient state that potentially spans multiple components....

That's exactly what I'm suggesting. :-)

By default component IDs are unique but you can specify a function that generates the key from props. This lets components “mirror” the same state or “transplant” it across the tree.

Collaborator

gaearon commented Jul 26, 2015

I don't know that it makes sense to couple transient state with the component. Maybe I'm misunderstanding but really I want transient state that potentially spans multiple components....

That's exactly what I'm suggesting. :-)

By default component IDs are unique but you can specify a function that generates the key from props. This lets components “mirror” the same state or “transplant” it across the tree.

@taylorhakes

This comment has been minimized.

Show comment
Hide comment
@taylorhakes

taylorhakes Jul 26, 2015

Contributor

My suggestion for this type of an API would be

@LocalState({ 
  keyFunc: (props) => //...
  reducers: [
    () => {}
   // ...
  ]
})
class MyContainer extends Component {
//...
}

The reason I like that slightly better is that you can use actions like normal, state is passed through props and you get the replay functionality. You also have the nice thing of not needing setState.

The child components also don't need to change based on whether they are using global or local state.

Contributor

taylorhakes commented Jul 26, 2015

My suggestion for this type of an API would be

@LocalState({ 
  keyFunc: (props) => //...
  reducers: [
    () => {}
   // ...
  ]
})
class MyContainer extends Component {
//...
}

The reason I like that slightly better is that you can use actions like normal, state is passed through props and you get the replay functionality. You also have the nice thing of not needing setState.

The child components also don't need to change based on whether they are using global or local state.

@CooCooCaCha

This comment has been minimized.

Show comment
Hide comment
@CooCooCaCha

CooCooCaCha Jul 26, 2015

I wonder if this could be used with the upcoming Stateless Functions feature in React?
https://github.com/reactjs/react-future/blob/master/01%20-%20Core/03%20-%20Stateless%20Functions.js

I wonder if this could be used with the upcoming Stateless Functions feature in React?
https://github.com/reactjs/react-future/blob/master/01%20-%20Core/03%20-%20Stateless%20Functions.js

@taylorhakes

This comment has been minimized.

Show comment
Hide comment
@taylorhakes

taylorhakes Jul 26, 2015

Contributor

Either one of the proposals could be used with stateless functions. As long as everything is passed through props, which it is.

Contributor

taylorhakes commented Jul 26, 2015

Either one of the proposals could be used with stateless functions. As long as everything is passed through props, which it is.

@taylorhakes

This comment has been minimized.

Show comment
Hide comment
@taylorhakes

taylorhakes Jul 30, 2015

Contributor

@gaearon I have created a successful experiment with pseudo-local state. It is located here
https://github.com/taylorhakes/redux-devtools/tree/local-global-state . Run the todo example app.

I will split it out into a separate repo shortly. The api closely follows my proposal mentioned earlier.

Contributor

taylorhakes commented Jul 30, 2015

@gaearon I have created a successful experiment with pseudo-local state. It is located here
https://github.com/taylorhakes/redux-devtools/tree/local-global-state . Run the todo example app.

I will split it out into a separate repo shortly. The api closely follows my proposal mentioned earlier.

@taylorhakes

This comment has been minimized.

Show comment
Hide comment
@taylorhakes

taylorhakes Jul 30, 2015

Contributor

In the app I changed the edit text of TodoTextInput from component state to pseudo-local and the isEditing flag in TodoItem to pseudo-local

Contributor

taylorhakes commented Jul 30, 2015

In the app I changed the edit text of TodoTextInput from component state to pseudo-local and the isEditing flag in TodoItem to pseudo-local

cef62 referenced this issue in cef62/redux-component-state Aug 25, 2015

Initial Implmentation of the lib
Current Code based on idea from this thread:
https://github.com/rackt/redux/issues/159 and initial experiments from
https://github.com/taylorhakes
@cef62

This comment has been minimized.

Show comment
Hide comment
@cef62

cef62 Aug 25, 2015

Following ideas from this discussion I've made a repo to support redux at component level: redux-component-state.

The current version is based on requirements of a specific project I'm working on but I want to add more features in the next weeks.

I'd like to have feedbacks and suggestions on it :)

cef62 commented Aug 25, 2015

Following ideas from this discussion I've made a repo to support redux at component level: redux-component-state.

The current version is based on requirements of a specific project I'm working on but I want to add more features in the next weeks.

I'd like to have feedbacks and suggestions on it :)

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 13, 2015

Contributor

We have been having a related discussion over in anthonyshort/deku#218.

IMO the custom key generation thing is critical, so critical that I almost thing it might be a good idea to eliminate the default of randomly generating them.

I also don't really like the idea of a setState. Why create an imperative abstraction just so that components can have their own state? People can certainly create a SET_LOCAL_STATE action if they want, but it seems like a bad practice to encourage, and maybe it makes sense to do this so that you can have interoperability with existing React components, but it seems like these should be two separate libraries. One that provides the basic component lifecycle bound state with no default id and no imperative setState, and another that takes that primitive and rebuilds React's local interface on top of it.

EDIT: I'd also add that TBH to me, the entire concept of component <-> state mapping, feels like it is a bit of a red herring. It is something that is very natural to think about, but might actually be deeply impractical to actually use? I don't have a great proposal for an alternative, but it is a nagging feeling that I have, that state should really not be considered in terms of the UI tree at all.

Contributor

ashaffer commented Sep 13, 2015

We have been having a related discussion over in anthonyshort/deku#218.

IMO the custom key generation thing is critical, so critical that I almost thing it might be a good idea to eliminate the default of randomly generating them.

I also don't really like the idea of a setState. Why create an imperative abstraction just so that components can have their own state? People can certainly create a SET_LOCAL_STATE action if they want, but it seems like a bad practice to encourage, and maybe it makes sense to do this so that you can have interoperability with existing React components, but it seems like these should be two separate libraries. One that provides the basic component lifecycle bound state with no default id and no imperative setState, and another that takes that primitive and rebuilds React's local interface on top of it.

EDIT: I'd also add that TBH to me, the entire concept of component <-> state mapping, feels like it is a bit of a red herring. It is something that is very natural to think about, but might actually be deeply impractical to actually use? I don't have a great proposal for an alternative, but it is a nagging feeling that I have, that state should really not be considered in terms of the UI tree at all.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 14, 2015

Contributor

A good example to think through I think is a dropdown (as is discussed in the linked thread). A dropdown has a local state (open/closed), but that state is also global, because a click anywhere other than the dropdown should close that dropdown. Usually people will do something like document.addEventListener('click') from inside their nested dropdown component, but that is a global listener for what was supposed to be a piece of local state.

My best thought at the moment for this is to create a higher-order duck for dropdowns that can be parameterized with your application's 'uncaught click on document' action. This doesn't solve the problem of lifecycle management or anything like that though.

Contributor

ashaffer commented Sep 14, 2015

A good example to think through I think is a dropdown (as is discussed in the linked thread). A dropdown has a local state (open/closed), but that state is also global, because a click anywhere other than the dropdown should close that dropdown. Usually people will do something like document.addEventListener('click') from inside their nested dropdown component, but that is a global listener for what was supposed to be a piece of local state.

My best thought at the moment for this is to create a higher-order duck for dropdowns that can be parameterized with your application's 'uncaught click on document' action. This doesn't solve the problem of lifecycle management or anything like that though.

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr Sep 14, 2015

Member

The dropdown may be a bit of a red herring.

The problem with document.addEventListener is it is a side effect, so it can't be directly controlled by your store. And the listener, while applied to the global context, is only concerned with this local context of your dropdown component. You don't want to have to run every single click in your document through the dispatcher, because that will be non-performant. You should be unbinding that listener after you're finished.

I think that's outside the bounds of application state, which is how I use redux. The selected item in a list is application state. The fact that the list is expanded is component state. It's all about what state comes into the component externally. No one upstream of my component tree will tell my dropdown to open, so why would they need to know about it?

Maybe some redux-y or react-redux-y wrappers around local state would be better? Something to give it similar behavior to my application state, but applied and only accessible at the local level.

Member

timdorr commented Sep 14, 2015

The dropdown may be a bit of a red herring.

The problem with document.addEventListener is it is a side effect, so it can't be directly controlled by your store. And the listener, while applied to the global context, is only concerned with this local context of your dropdown component. You don't want to have to run every single click in your document through the dispatcher, because that will be non-performant. You should be unbinding that listener after you're finished.

I think that's outside the bounds of application state, which is how I use redux. The selected item in a list is application state. The fact that the list is expanded is component state. It's all about what state comes into the component externally. No one upstream of my component tree will tell my dropdown to open, so why would they need to know about it?

Maybe some redux-y or react-redux-y wrappers around local state would be better? Something to give it similar behavior to my application state, but applied and only accessible at the local level.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 14, 2015

Contributor

@timdorr I think you are right about the use of document.addEventListener. That should be bound/unbound as needed by the action creator, I suppose.

However, why do you say noone upstream of your component might tell it to open? Also, i'm not sure I understand your distinction between 'application state' and 'component state' here. It is very possible that someone outside of your dropdown may need to know about it. Sometimes you want to move things around slightly when a dropdown is open, or do other things like that.

I think, in general, making distinctions between types of state is a very slippery slope, and the more we can unify it all into redux's global state atom the better.

Contributor

ashaffer commented Sep 14, 2015

@timdorr I think you are right about the use of document.addEventListener. That should be bound/unbound as needed by the action creator, I suppose.

However, why do you say noone upstream of your component might tell it to open? Also, i'm not sure I understand your distinction between 'application state' and 'component state' here. It is very possible that someone outside of your dropdown may need to know about it. Sometimes you want to move things around slightly when a dropdown is open, or do other things like that.

I think, in general, making distinctions between types of state is a very slippery slope, and the more we can unify it all into redux's global state atom the better.

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr Sep 14, 2015

Member

If someone else needs to know about your dropdown state, then it's no longer local component state and is, instead, application state and should be managed by redux. However, if we're thinking declaratively, the fact that the state indicates an open dropdown is just an implementation detail of the component.

If you need to move things around, that sounds more like a CSS problem than an application problem. Or at the very least, that would be local state contained at a higher, but not top-level, container.

Member

timdorr commented Sep 14, 2015

If someone else needs to know about your dropdown state, then it's no longer local component state and is, instead, application state and should be managed by redux. However, if we're thinking declaratively, the fact that the state indicates an open dropdown is just an implementation detail of the component.

If you need to move things around, that sounds more like a CSS problem than an application problem. Or at the very least, that would be local state contained at a higher, but not top-level, container.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 14, 2015

Contributor

@timdorr Ya, I think what i'm saying is that there isn't really such a thing as local component state, and that trying to create and enforce strong component state boundaries is a mistake.

Also, in relation to the document click issue, while I do think you're right about that in particular, I think the general problem is still valid. Your application has application-specific but global events that take place, and your sub-components may want to respond to those events, so I do think you need a way of parameterizing things like dropdowns with event types expressed in the language of your application. Examples might be closing modals/dropdowns on route change, or similar.

Contributor

ashaffer commented Sep 14, 2015

@timdorr Ya, I think what i'm saying is that there isn't really such a thing as local component state, and that trying to create and enforce strong component state boundaries is a mistake.

Also, in relation to the document click issue, while I do think you're right about that in particular, I think the general problem is still valid. Your application has application-specific but global events that take place, and your sub-components may want to respond to those events, so I do think you need a way of parameterizing things like dropdowns with event types expressed in the language of your application. Examples might be closing modals/dropdowns on route change, or similar.

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr Sep 14, 2015

Member

I disagree. There is inherently local state because of the actual rendered view (DOM, in this case) having some state itself. React does a great job of normalizing the behavior of that state, but there is an inherent externality to it. In particular, there is state we cannot manage, such as the value of file inputs.

I don't think redux should be the do-all, end-all source of state in the entire javascript environment. It doesn't handle side effects at all, so it cannot be the ultimate state authority. It's best for managing application state, so it should be limited to that use. Trying to map and synchronize every little bit of state throughout your components and the DOM back to a single location is undoubtedly never going to be performant and suffer from quirks of that translation.

I happen to enjoy the boundaries of component and application state, as they are rarely intermingled more than one level up my component tree. My smart components act as a gatekeeper to dumb components that don't concern themselves with external effects. They provide APIs for my containers to compose them together logically. YMMV but it's been working great for me.

Member

timdorr commented Sep 14, 2015

I disagree. There is inherently local state because of the actual rendered view (DOM, in this case) having some state itself. React does a great job of normalizing the behavior of that state, but there is an inherent externality to it. In particular, there is state we cannot manage, such as the value of file inputs.

I don't think redux should be the do-all, end-all source of state in the entire javascript environment. It doesn't handle side effects at all, so it cannot be the ultimate state authority. It's best for managing application state, so it should be limited to that use. Trying to map and synchronize every little bit of state throughout your components and the DOM back to a single location is undoubtedly never going to be performant and suffer from quirks of that translation.

I happen to enjoy the boundaries of component and application state, as they are rarely intermingled more than one level up my component tree. My smart components act as a gatekeeper to dumb components that don't concern themselves with external effects. They provide APIs for my containers to compose them together logically. YMMV but it's been working great for me.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 14, 2015

Contributor

I am working on the side-effects issue over here redux-effects :-), I mediate all my effects this way, and it is working out pretty well so far.

That being said, how would you create a reusable dropdown component? Assume that you want to close your dropdowns when the route changes. If the dropdown stores its own state, you have to somehow inform the dropdown of this event. How would you accomplish that?

Contributor

ashaffer commented Sep 14, 2015

I am working on the side-effects issue over here redux-effects :-), I mediate all my effects this way, and it is working out pretty well so far.

That being said, how would you create a reusable dropdown component? Assume that you want to close your dropdowns when the route changes. If the dropdown stores its own state, you have to somehow inform the dropdown of this event. How would you accomplish that?

@mindjuice

This comment has been minimized.

Show comment
Hide comment
@mindjuice

mindjuice Sep 15, 2015

Contributor

I used to believe in the separation of component state and application state, and have had several spirited "conversations" defending it. However, over the course of working on two pretty decent sized SPAs over the past year, I pretty much always end up wanting to know about that state or affect it from somewhere else (the dropdown example came up a few times).

The problem with encapsulating some component state locally, as mentioned above, is that there is then no way for another part of the app to know about it or affect it.

By putting it in the state tree instead, even if nobody wants to use it right now, it's not hurting anything. When the time comes that it is needed (and it usually does), it's available, like all other state.

There can certainly be performance issues in some cases (e.g., triggering component render off every keypress in an edit box), but when I've encountered performance issues, a bit of component hierarchy refactoring usually fixes the problem.

I haven't yet used redux-component-state yet, but am interested in looking into that more.

Contributor

mindjuice commented Sep 15, 2015

I used to believe in the separation of component state and application state, and have had several spirited "conversations" defending it. However, over the course of working on two pretty decent sized SPAs over the past year, I pretty much always end up wanting to know about that state or affect it from somewhere else (the dropdown example came up a few times).

The problem with encapsulating some component state locally, as mentioned above, is that there is then no way for another part of the app to know about it or affect it.

By putting it in the state tree instead, even if nobody wants to use it right now, it's not hurting anything. When the time comes that it is needed (and it usually does), it's available, like all other state.

There can certainly be performance issues in some cases (e.g., triggering component render off every keypress in an edit box), but when I've encountered performance issues, a bit of component hierarchy refactoring usually fixes the problem.

I haven't yet used redux-component-state yet, but am interested in looking into that more.

@cesarandreu

This comment has been minimized.

Show comment
Hide comment
@cesarandreu

cesarandreu Sep 15, 2015

Contributor

I keep ephemeral state in my components. My tooltips/dropdowns/popovers are all controlled components, and I have a wrapper to automatically handle opening and closing it on hover/focus/click. That component keeps its own state, and it has no interactions with redux, because no part of the app cares about it being opened or closed. If some part of the app cares about its state, I'll use the controlled component and add it to redux's state. But why would I do that if it's not needed?

My popovers/tooltips all get closed when I navigate because they get unmounted. If they have have single-click actions (such as in a dropdown menu), I intercept click events.

If it's something more complicated, such as a form inside of a popover, I'll track that state in redux.

react-overlays is great.

Contributor

cesarandreu commented Sep 15, 2015

I keep ephemeral state in my components. My tooltips/dropdowns/popovers are all controlled components, and I have a wrapper to automatically handle opening and closing it on hover/focus/click. That component keeps its own state, and it has no interactions with redux, because no part of the app cares about it being opened or closed. If some part of the app cares about its state, I'll use the controlled component and add it to redux's state. But why would I do that if it's not needed?

My popovers/tooltips all get closed when I navigate because they get unmounted. If they have have single-click actions (such as in a dropdown menu), I intercept click events.

If it's something more complicated, such as a form inside of a popover, I'll track that state in redux.

react-overlays is great.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 15, 2015

Contributor

@cesarandreu I should have been slightly more clear, sorry. Assume the dropdown is also outside the scope of the router (e.g. in a two-pane app, where the nav is constant). In this situation it seems to me you're left with no great options if you want to use your dropdown component that has its own state.

The problem really comes in when you want to create reusable components that have state, I think. Because inevitably what happens is that eventually one of the places where you want to use it wants to manage its state differently.

Maybe the rule should just be that reusable components shouldn't have state, and all state should be isolated to one-off components? But even that seems like a somewhat brittle abstraction.

Contributor

ashaffer commented Sep 15, 2015

@cesarandreu I should have been slightly more clear, sorry. Assume the dropdown is also outside the scope of the router (e.g. in a two-pane app, where the nav is constant). In this situation it seems to me you're left with no great options if you want to use your dropdown component that has its own state.

The problem really comes in when you want to create reusable components that have state, I think. Because inevitably what happens is that eventually one of the places where you want to use it wants to manage its state differently.

Maybe the rule should just be that reusable components shouldn't have state, and all state should be isolated to one-off components? But even that seems like a somewhat brittle abstraction.

@cef62

This comment has been minimized.

Show comment
Hide comment
@cef62

cef62 Sep 15, 2015

@mindjuice my experience it's similar to yours. With redux-component-state I'm trying to address this kind of problems. Currently the store enhancer fulfills my needs but I'm not quite satisfied.
Main issue I'd like to address asap is to add a better way to enable access to component-states from higher order tools. I believe that a component state should be known only to its owner but to improve support to other tools during development phase component private states should be accessible.
Also the middleware APIs for component-state could and will be improved.
These days I'm on deadline with a project, but I want to work on redux-component-state as soon as possible.

If you'll try the project any feedback will be very appreciated!

cef62 commented Sep 15, 2015

@mindjuice my experience it's similar to yours. With redux-component-state I'm trying to address this kind of problems. Currently the store enhancer fulfills my needs but I'm not quite satisfied.
Main issue I'd like to address asap is to add a better way to enable access to component-states from higher order tools. I believe that a component state should be known only to its owner but to improve support to other tools during development phase component private states should be accessible.
Also the middleware APIs for component-state could and will be improved.
These days I'm on deadline with a project, but I want to work on redux-component-state as soon as possible.

If you'll try the project any feedback will be very appreciated!

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 16, 2015

Contributor

I have created a library that might be helpful in exploring this concept:

vdux

Basically it just creates a cycle between your redux store and a virtual-dom returning function. Event handlers bound to elements return your actions (as raw objects - not bound to dispatch). vdux takes care of dispatching them for you, so no more bindActionCreators. Since there is no need to bind your action creators beforehand, it gets much easier to write modular components with their own actions and action creators, and also to compose your action creators if you wanted to implement a strategy similar to functional-frontend-architecture (i.e. decorating your actions with locality information as they ascend the tree). For the reducers...

I am also working on an ephemeral state management helper that isn't bound to any particular view rendering engine: redux-ephemeral. It accomplishes the same thing as redux-component-state, but it isn't coupled to React so you can use it with vdux, but because of that it takes a bit more wiring on your end to make it work. It seems to me the essence of the local component state problem is not the locality, but the ephemerality. We want little slices of state that are bound to the lifecycle of a particular component (or perhaps N components via reference counting?).

Contributor

ashaffer commented Sep 16, 2015

I have created a library that might be helpful in exploring this concept:

vdux

Basically it just creates a cycle between your redux store and a virtual-dom returning function. Event handlers bound to elements return your actions (as raw objects - not bound to dispatch). vdux takes care of dispatching them for you, so no more bindActionCreators. Since there is no need to bind your action creators beforehand, it gets much easier to write modular components with their own actions and action creators, and also to compose your action creators if you wanted to implement a strategy similar to functional-frontend-architecture (i.e. decorating your actions with locality information as they ascend the tree). For the reducers...

I am also working on an ephemeral state management helper that isn't bound to any particular view rendering engine: redux-ephemeral. It accomplishes the same thing as redux-component-state, but it isn't coupled to React so you can use it with vdux, but because of that it takes a bit more wiring on your end to make it work. It seems to me the essence of the local component state problem is not the locality, but the ephemerality. We want little slices of state that are bound to the lifecycle of a particular component (or perhaps N components via reference counting?).

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 16, 2015

Collaborator

We want little slices of state that are bound to the lifecycle of a particular component (or perhaps N components via reference counting?).

I was thinking whether it's possible to make a store enhancer that would let you "branch" ephemeral stores that have the standard { getState, dispatch, subscribe } API but are "mounted" to the real store.

Collaborator

gaearon commented Sep 16, 2015

We want little slices of state that are bound to the lifecycle of a particular component (or perhaps N components via reference counting?).

I was thinking whether it's possible to make a store enhancer that would let you "branch" ephemeral stores that have the standard { getState, dispatch, subscribe } API but are "mounted" to the real store.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 16, 2015

Collaborator

e.g.

import { compose, createStore } from 'redux';
import ephemeralize, { mountStore, unmountStore } from 'redux-ephemeral';

let finalCreateStore = ephemeralize(createStore);
let store = finalCreateStore(reducer);

class SomeComponent {
  componentWillMount() {
    this.storeA = store.dispatch(mountStore('a'));
    this.storeB = store.dispatch(mountStore('b'));
  }

  componentWillUnmount() {
    store.dispatch(unmountStore('a'));
    store.dispatch(unmountStore('b'));
  }

  render() {
    return (
      <div>
        <Provider store={this.storeA}><SomeConnectedReduxComponent /></Provider>
        <Provider store={this.storeB}><SomeConnectedReduxComponent /></Provider>
      </div>
    );
  }
}
Collaborator

gaearon commented Sep 16, 2015

e.g.

import { compose, createStore } from 'redux';
import ephemeralize, { mountStore, unmountStore } from 'redux-ephemeral';

let finalCreateStore = ephemeralize(createStore);
let store = finalCreateStore(reducer);

class SomeComponent {
  componentWillMount() {
    this.storeA = store.dispatch(mountStore('a'));
    this.storeB = store.dispatch(mountStore('b'));
  }

  componentWillUnmount() {
    store.dispatch(unmountStore('a'));
    store.dispatch(unmountStore('b'));
  }

  render() {
    return (
      <div>
        <Provider store={this.storeA}><SomeConnectedReduxComponent /></Provider>
        <Provider store={this.storeB}><SomeConnectedReduxComponent /></Provider>
      </div>
    );
  }
}
@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 17, 2015

Contributor

Ya, mounting stores seems like a very clean abstraction to me. I'm just not sure how it would work implementation-wise. It seems like you could do the mounting with replaceReducer, but how would you unmount?

I think it's also important to ensure that even though that store is mounted, the component that mounted it doesn't 'own' the state. It's still accessible to all other reducers and components.

Contributor

ashaffer commented Sep 17, 2015

Ya, mounting stores seems like a very clean abstraction to me. I'm just not sure how it would work implementation-wise. It seems like you could do the mounting with replaceReducer, but how would you unmount?

I think it's also important to ensure that even though that store is mounted, the component that mounted it doesn't 'own' the state. It's still accessible to all other reducers and components.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 17, 2015

Collaborator

Ya, mounting stores seems like a very clean abstraction to me. I'm just not sure how it would work implementation-wise. It seems like you could do the mounting with replaceReducer, but how would you unmount?

The mounted things aren't real stores—they're just projections with a Store-like API. In reality, they're like

// lib code

getState() {
  return realStore.getState().localStores[localStoreKey];
}

dispatch() {
  return realStore.dispatch({
    type: 'LOCAL_STORE_ACTION',
    key: localStoreKey,
    action: action
}

subscribe(listener) {
  return realStore.subscribe(listener); // bonus: whether realStore.getState().localStores[localStoreKey] changed
}

Therefore there is no need for replaceReducer—just make sure to mount a localStoreReducer:

// app code

combineReducers({
  normalTodos: todos,
  localStores: createLocalStoresReducer()
})
// lib code
function createLocalStoresReducer(state, action) {
  switch (state) {
  case CREATE_LOCAL_STORE:
    return { ...state, [action.key]: action.reducer(undefined, { type: 'init' }) }
  case LOCAL_STORE_ACTION:
    return { ...state, [action.key]: 
     // lol whatever I haven't thought this through
  }
}

as you see I have no real idea what I'm talking about lol. But if it works with record/replay and time travel then it's done right.

Collaborator

gaearon commented Sep 17, 2015

Ya, mounting stores seems like a very clean abstraction to me. I'm just not sure how it would work implementation-wise. It seems like you could do the mounting with replaceReducer, but how would you unmount?

The mounted things aren't real stores—they're just projections with a Store-like API. In reality, they're like

// lib code

getState() {
  return realStore.getState().localStores[localStoreKey];
}

dispatch() {
  return realStore.dispatch({
    type: 'LOCAL_STORE_ACTION',
    key: localStoreKey,
    action: action
}

subscribe(listener) {
  return realStore.subscribe(listener); // bonus: whether realStore.getState().localStores[localStoreKey] changed
}

Therefore there is no need for replaceReducer—just make sure to mount a localStoreReducer:

// app code

combineReducers({
  normalTodos: todos,
  localStores: createLocalStoresReducer()
})
// lib code
function createLocalStoresReducer(state, action) {
  switch (state) {
  case CREATE_LOCAL_STORE:
    return { ...state, [action.key]: action.reducer(undefined, { type: 'init' }) }
  case LOCAL_STORE_ACTION:
    return { ...state, [action.key]: 
     // lol whatever I haven't thought this through
  }
}

as you see I have no real idea what I'm talking about lol. But if it works with record/replay and time travel then it's done right.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 17, 2015

Contributor

Ahh yes of course. Ya that's basically what i'm doing in redux-ephemeral, but exposing an api that looks like mounting stores is nice.

I think one problem with it would be the extra subscribes. The mounting interface implies that components may subscribe to their little sub-store, but if you have already subscribed your whole app at the top of the tree, you might get duplicate updates.

Also, I think maybe the localization of the ephemeral stores that I am doing in redux-ephemeral right now might actually be a mistake. Action filtering by key should maybe be curried into the reducer if people want that, so that the ephemeral reducers may still respond to global actions. E.g.

function componentWillMount () {
    this.storeA = store.dispatch(mountStore('a', makeReducer('a')));
    this.storeB = store.dispatch(mountStore('b', makeReducer('b')));
}

function makeReducer (key) {
  return (state, action) => {
    if (action.type === GLOBAL_ACTION) {
     // respond locally to a global thing
    }
    if (action.key === key) {
      switch (action.type) {
        // respond locally to a local thing
      }
    }

    return state
  }
}

Makes a little more work for the user, but adds a lot of flexibility I think. Especially in the realm of higher-order components that you can parameterize with the application's global action types.

Contributor

ashaffer commented Sep 17, 2015

Ahh yes of course. Ya that's basically what i'm doing in redux-ephemeral, but exposing an api that looks like mounting stores is nice.

I think one problem with it would be the extra subscribes. The mounting interface implies that components may subscribe to their little sub-store, but if you have already subscribed your whole app at the top of the tree, you might get duplicate updates.

Also, I think maybe the localization of the ephemeral stores that I am doing in redux-ephemeral right now might actually be a mistake. Action filtering by key should maybe be curried into the reducer if people want that, so that the ephemeral reducers may still respond to global actions. E.g.

function componentWillMount () {
    this.storeA = store.dispatch(mountStore('a', makeReducer('a')));
    this.storeB = store.dispatch(mountStore('b', makeReducer('b')));
}

function makeReducer (key) {
  return (state, action) => {
    if (action.type === GLOBAL_ACTION) {
     // respond locally to a global thing
    }
    if (action.key === key) {
      switch (action.type) {
        // respond locally to a local thing
      }
    }

    return state
  }
}

Makes a little more work for the user, but adds a lot of flexibility I think. Especially in the realm of higher-order components that you can parameterize with the application's global action types.

@ghost ghost referenced this issue Sep 18, 2015

Closed

Application Plugins? #750

@AndyMoreland

This comment has been minimized.

Show comment
Hide comment
@AndyMoreland

AndyMoreland Sep 18, 2015

I haven't packaged this up nicely or anything but I have independent arrived at a solution that looks very similar to the code @ashaffer provided above.

I've found that my "subcomponent reducers" do in fact need to respond to global actions in addition to local actions in order to get things done, and I do end up having a sort of global "public action API" that subcomponents can depend on.

My solution differs from ideas discussed above in that I consider the subcomponent state private to the component in question even though it is stored in the global store. There is nothing that stops you from looking up a subcomponent's state if you know its componentId, but it breaks the encapsulation that I want to enforce and I avoid doing this.

More information:

I implement runtime mounting of reducers by registering a reducer for each subcomponent when it is first connect'd (I wrote a subcomponentConnect function that does this for me) and by dispatching actions to it if they are tagged with an appropriate componentId.

This subcomponentConnect function wraps the standard store.dispatch and wraps any actions generated by subcomponents with a SUBCOMPONENT_ACTION action that includes a componentId. The global reducer unpacks these and handles the dispatching as noted above. My global de-thunking and logging middlewares also understand SUBCOMPONENT_ACTIONs and do intelligent things.

The subcomponentConnect also implicitly includes a selector: whenever the wrapped subcomponent is rendered, its state is merged into the props.

In my solution I do not support subscribe for subcomponent state as a first-class thing: I considered this briefly but found that I did not require it anywhere in my application. I might include it again in the future.

I haven't packaged this up nicely or anything but I have independent arrived at a solution that looks very similar to the code @ashaffer provided above.

I've found that my "subcomponent reducers" do in fact need to respond to global actions in addition to local actions in order to get things done, and I do end up having a sort of global "public action API" that subcomponents can depend on.

My solution differs from ideas discussed above in that I consider the subcomponent state private to the component in question even though it is stored in the global store. There is nothing that stops you from looking up a subcomponent's state if you know its componentId, but it breaks the encapsulation that I want to enforce and I avoid doing this.

More information:

I implement runtime mounting of reducers by registering a reducer for each subcomponent when it is first connect'd (I wrote a subcomponentConnect function that does this for me) and by dispatching actions to it if they are tagged with an appropriate componentId.

This subcomponentConnect function wraps the standard store.dispatch and wraps any actions generated by subcomponents with a SUBCOMPONENT_ACTION action that includes a componentId. The global reducer unpacks these and handles the dispatching as noted above. My global de-thunking and logging middlewares also understand SUBCOMPONENT_ACTIONs and do intelligent things.

The subcomponentConnect also implicitly includes a selector: whenever the wrapped subcomponent is rendered, its state is merged into the props.

In my solution I do not support subscribe for subcomponent state as a first-class thing: I considered this briefly but found that I did not require it anywhere in my application. I might include it again in the future.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 20, 2015

Contributor

I realized today, and i'm not sure if you were suggesting this already @gaearon, but what you really want is for parent component's to mount their children, not for components to mount themselves. E.g.

// Let's say this is a nav component
function beforeMount (props) {
  // props.stateKey is an identifier that represents our current component's local state in the tree
  actions.createEphemeralStore(props.stateKey, 'dropdown', Dropdown)
}

function render (props) {
  return <Dropdown state={props.dropdown} />
}

Then that component's parent is doing something like:

function beforeMount (props) {
  actions.createEphemeralStore('nav', Nav)
}

function render (props) {
  return <Nav {...props.nav} />
}

So dropdown itself doesn't actually create or destroy any ephemeral state. Though it could export a creator for its own state export createDropdown = key => actions.createEphemeralStore(key, dropdownReducer).

It's slightly syntactically cumbersome relative to components just automatically getting their own state, but I think it makes the couplings really explicit in kind of a nice way. Your pre-mounting function then identifies all of your sub-components that have state, and can put them wherever it deems most appropriate - and also then by definition has access to them.

It's also super easy to create nice decorators for the mount/unmount hooks to make the syntax really minimal.

PS: Sorry I keep using non-react hook names and syntax, I don't use it so I can never remember their names for these things when i'm writing this stuff. I hope everything is still clear.

Contributor

ashaffer commented Sep 20, 2015

I realized today, and i'm not sure if you were suggesting this already @gaearon, but what you really want is for parent component's to mount their children, not for components to mount themselves. E.g.

// Let's say this is a nav component
function beforeMount (props) {
  // props.stateKey is an identifier that represents our current component's local state in the tree
  actions.createEphemeralStore(props.stateKey, 'dropdown', Dropdown)
}

function render (props) {
  return <Dropdown state={props.dropdown} />
}

Then that component's parent is doing something like:

function beforeMount (props) {
  actions.createEphemeralStore('nav', Nav)
}

function render (props) {
  return <Nav {...props.nav} />
}

So dropdown itself doesn't actually create or destroy any ephemeral state. Though it could export a creator for its own state export createDropdown = key => actions.createEphemeralStore(key, dropdownReducer).

It's slightly syntactically cumbersome relative to components just automatically getting their own state, but I think it makes the couplings really explicit in kind of a nice way. Your pre-mounting function then identifies all of your sub-components that have state, and can put them wherever it deems most appropriate - and also then by definition has access to them.

It's also super easy to create nice decorators for the mount/unmount hooks to make the syntax really minimal.

PS: Sorry I keep using non-react hook names and syntax, I don't use it so I can never remember their names for these things when i'm writing this stuff. I hope everything is still clear.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 21, 2015

Contributor

Alright guys, I have a working solution for vdux, and a strong primitive for redux, I think:

  • redux-ephemeral - This is a low-level local state library. You can create, destroy, and update pieces of local state and dynamically assign reducers to it. You could use this directly in your components, but it'd kind of suck.
  • vdux-local - Uses redux-ephemeral and composes around your component to automatically create and destroy local pieces of state based on the key prop. Also exports a localAction action-creator creator so your components may export actions that pertain to them, and you can direct those actions to them by passing the appropriate key. By default it will pass along a setState to your render function and all your hooks, just like react. But you may also export your own reducer and action creators, and they will be composed.
  • virtual-component - A wrapper around thunk creation for virtual-dom that gives you a more react/deku like API with hooks, shouldUpdate and all that good stuff.

There is a working, though extremely non-pretty example in vdux right now. It is the 'todo' example. It should be pretty straightforward to create a variant of vdux-local on top of redux-ephemeral for whatever your preferred virtual dom framework is.

Contributor

ashaffer commented Sep 21, 2015

Alright guys, I have a working solution for vdux, and a strong primitive for redux, I think:

  • redux-ephemeral - This is a low-level local state library. You can create, destroy, and update pieces of local state and dynamically assign reducers to it. You could use this directly in your components, but it'd kind of suck.
  • vdux-local - Uses redux-ephemeral and composes around your component to automatically create and destroy local pieces of state based on the key prop. Also exports a localAction action-creator creator so your components may export actions that pertain to them, and you can direct those actions to them by passing the appropriate key. By default it will pass along a setState to your render function and all your hooks, just like react. But you may also export your own reducer and action creators, and they will be composed.
  • virtual-component - A wrapper around thunk creation for virtual-dom that gives you a more react/deku like API with hooks, shouldUpdate and all that good stuff.

There is a working, though extremely non-pretty example in vdux right now. It is the 'todo' example. It should be pretty straightforward to create a variant of vdux-local on top of redux-ephemeral for whatever your preferred virtual dom framework is.

@timaschew

This comment has been minimized.

Show comment
Hide comment
@timaschew

timaschew Sep 29, 2015

I don't get it, why it's bad to use setState of React?

I don't get it, why it's bad to use setState of React?

@dshimkoski

This comment has been minimized.

Show comment
Hide comment
@dshimkoski

dshimkoski Sep 29, 2015

One example would be application state that gets sent to a logger when an exception is caught. If you hide transient state in the component as an implementation detail, you may not be able to reproduce the issue, since the necessary condition may only be present when you click on X while Y and Z are open but A and B are closed.

One example would be application state that gets sent to a logger when an exception is caught. If you hide transient state in the component as an implementation detail, you may not be able to reproduce the issue, since the necessary condition may only be present when you click on X while Y and Z are open but A and B are closed.

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Sep 30, 2015

Contributor

@timaschew Partially it's just the aesthetics of truly having all state in one place. There are some practical benefits though:

  • Logging, as @dshimkoski mentioned.
  • Global undo, time-travel, etc..
  • Manipulating state programmatically, e.g. copying state from one component to another, reparenting a component in another place in the tree
  • Freeing state from React more generally. When state is trapped inside react, it's this thing you can't reason about and metaprogram around - you're stuck with the limited paradigm they give you, which admittedly covers a lot of cases, but it does prevent people from exploring alternative state management abstractions, or creating more interesting state handling patterns and libraries.

Another thing to point out is that react components are not actually pure - they own their own state. Migrating state into redux allows you to have a truly pure render function, if that is important to you.

Contributor

ashaffer commented Sep 30, 2015

@timaschew Partially it's just the aesthetics of truly having all state in one place. There are some practical benefits though:

  • Logging, as @dshimkoski mentioned.
  • Global undo, time-travel, etc..
  • Manipulating state programmatically, e.g. copying state from one component to another, reparenting a component in another place in the tree
  • Freeing state from React more generally. When state is trapped inside react, it's this thing you can't reason about and metaprogram around - you're stuck with the limited paradigm they give you, which admittedly covers a lot of cases, but it does prevent people from exploring alternative state management abstractions, or creating more interesting state handling patterns and libraries.

Another thing to point out is that react components are not actually pure - they own their own state. Migrating state into redux allows you to have a truly pure render function, if that is important to you.

@timaschew

This comment has been minimized.

Show comment
Hide comment
@timaschew

timaschew Oct 1, 2015

okay, I understand if the you have at least one of the needs you listet.
But for components which has a really simple UI state, which don't need to be "reload safe" and which you don't need to transfer or to log and this state is also completely standalone, for instance:

a component with a help icon, and if you click on that icon you want to display a help text, the visibility is toggled on each click on the help icon.

I don't see here a reason to pass this to the whole redux flow

okay, I understand if the you have at least one of the needs you listet.
But for components which has a really simple UI state, which don't need to be "reload safe" and which you don't need to transfer or to log and this state is also completely standalone, for instance:

a component with a help icon, and if you click on that icon you want to display a help text, the visibility is toggled on each click on the help icon.

I don't see here a reason to pass this to the whole redux flow

@ashaffer

This comment has been minimized.

Show comment
Hide comment
@ashaffer

ashaffer Oct 1, 2015

Contributor

@timaschew Ya, I think it just comes down to what is important to you. I certainly wouldn't say that it's wrong to use React's local component state. But the ultimate goal here, for me at least, is to have the primitive ui = app(state), where app is a pure function that is 1:1 with values of state. When you have local component state, app(state) may return different values for ui based on what's in those local components' states, which destroys all of the guarantees of functional purity.

In your help example, that little piece of local state in your help component infects the entire tree above it. Now everything that contains help is impure, and you can no longer infer from its arguments exactly what it will return - you have to actually run it to see.

The benefits of doing this are definitely a little abstract for now because the benefits of it are largely in more powerful abstraction, code clarity (ofc a matter of opinion here), tooling, and automated verification capabilities.

Contributor

ashaffer commented Oct 1, 2015

@timaschew Ya, I think it just comes down to what is important to you. I certainly wouldn't say that it's wrong to use React's local component state. But the ultimate goal here, for me at least, is to have the primitive ui = app(state), where app is a pure function that is 1:1 with values of state. When you have local component state, app(state) may return different values for ui based on what's in those local components' states, which destroys all of the guarantees of functional purity.

In your help example, that little piece of local state in your help component infects the entire tree above it. Now everything that contains help is impure, and you can no longer infer from its arguments exactly what it will return - you have to actually run it to see.

The benefits of doing this are definitely a little abstract for now because the benefits of it are largely in more powerful abstraction, code clarity (ofc a matter of opinion here), tooling, and automated verification capabilities.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Oct 5, 2015

Collaborator

Thanks for the discussion! I'm closing this as inconclusive.
A better idea might be to work on enhancing React state model: facebook/react#4595
Pseudo-local state adds too much indirection for my taste, but if you're fine with it, see approaches above.

Collaborator

gaearon commented Oct 5, 2015

Thanks for the discussion! I'm closing this as inconclusive.
A better idea might be to work on enhancing React state model: facebook/react#4595
Pseudo-local state adds too much indirection for my taste, but if you're fine with it, see approaches above.

@gaearon gaearon closed this Oct 5, 2015

@rygine

This comment has been minimized.

Show comment
Hide comment
@rygine

rygine Oct 7, 2015

A little late to the party, but I put something together and it seems to work quite well. It needs a lot of refinement. I'm sure there's a better way to do this.

I overrode setState so that components can be used in other projects, outside of Redux.

https://gist.github.com/rygine/4e6c08e32c9760249d66

rygine commented Oct 7, 2015

A little late to the party, but I put something together and it seems to work quite well. It needs a lot of refinement. I'm sure there's a better way to do this.

I overrode setState so that components can be used in other projects, outside of Redux.

https://gist.github.com/rygine/4e6c08e32c9760249d66

@tonyhb

This comment has been minimized.

Show comment
Hide comment
@tonyhb

tonyhb Dec 8, 2015

Also made a small library adapting react-redux via decorators for UI management. It's a WIP but seems like it will work well for our projects at Docker: https://github.com/tonyhb/redux-ui

tonyhb commented Dec 8, 2015

Also made a small library adapting react-redux via decorators for UI management. It's a WIP but seems like it will work well for our projects at Docker: https://github.com/tonyhb/redux-ui

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Feb 12, 2016

Collaborator

There’s been some interesting work in https://github.com/threepointone/redux-react-local.

Collaborator

gaearon commented Feb 12, 2016

There’s been some interesting work in https://github.com/threepointone/redux-react-local.

@threepointone

This comment has been minimized.

Show comment
Hide comment
@threepointone

threepointone Feb 14, 2016

Thanks for the mention, Dan. I got some work into this over the weekend, and it seem to match up with your original RFC. A couple of notes -

  • you can't have have two components with the same ident alive on the page at the same time(because this would also imply 2 reducers for the same key, which wouldn't make sense). That said, you could maybe still transplant/mirror the state by simply using @connect(state => state.local[ident])and piping it to the same dumb rendering component the original component uses.
  • when registering local stores, I'm sending functions in the action payload, and even storing them in the redux atom, which would usually be a no-no in the redux world, if only because it would break server side rendering. However, because just rendering the component creates the sideeffect of registering the reducer, we can simply remove the reducers from the atom before serializing it and sending it over the wire to hydrate an app. On the client side, they'd kick in when rendered down, pick up the hydrated data, and carry on. So, it should just mostly work, but I'd have to actually try and see how.

Thanks for the mention, Dan. I got some work into this over the weekend, and it seem to match up with your original RFC. A couple of notes -

  • you can't have have two components with the same ident alive on the page at the same time(because this would also imply 2 reducers for the same key, which wouldn't make sense). That said, you could maybe still transplant/mirror the state by simply using @connect(state => state.local[ident])and piping it to the same dumb rendering component the original component uses.
  • when registering local stores, I'm sending functions in the action payload, and even storing them in the redux atom, which would usually be a no-no in the redux world, if only because it would break server side rendering. However, because just rendering the component creates the sideeffect of registering the reducer, we can simply remove the reducers from the atom before serializing it and sending it over the wire to hydrate an app. On the client side, they'd kick in when rendered down, pick up the hydrated data, and carry on. So, it should just mostly work, but I'd have to actually try and see how.
@threepointone

This comment has been minimized.

Show comment
Hide comment
@threepointone

threepointone Feb 15, 2016

I take back what I said about 'transplanting', got it to work just fine. You can now render the same component anywhere and it syncs state/behavior, just fine. A gif -
4xrqefdnev

for this code - https://github.com/threepointone/redux-react-local/blob/master/example/transplant.js

I take back what I said about 'transplanting', got it to work just fine. You can now render the same component anywhere and it syncs state/behavior, just fine. A gif -
4xrqefdnev

for this code - https://github.com/threepointone/redux-react-local/blob/master/example/transplant.js

@threepointone

This comment has been minimized.

Show comment
Hide comment
@threepointone

threepointone Feb 18, 2016

server side rendering/rehydration seems to work fine ootb too, except when you need to dispatch actions/prepopulate these stores. For that, I had to introduce 2 small helpers to 1. populate a store with local reducers 2. 'sanitize' a store's state to be JSON.stringify safe. 'working' example here https://github.com/threepointone/redux-react-local/blob/master/example/server.js

server side rendering/rehydration seems to work fine ootb too, except when you need to dispatch actions/prepopulate these stores. For that, I had to introduce 2 small helpers to 1. populate a store with local reducers 2. 'sanitize' a store's state to be JSON.stringify safe. 'working' example here https://github.com/threepointone/redux-react-local/blob/master/example/server.js

@denis-sokolov

This comment has been minimized.

Show comment
Hide comment
@denis-sokolov

denis-sokolov Mar 25, 2016

redux-cursor project might be of interest to readers in this issue. It’s an implementation of cursors avoiding the primary disadvantage gaearon notes.

redux-cursor project might be of interest to readers in this issue. It’s an implementation of cursors avoiding the primary disadvantage gaearon notes.

@gcazaciuc

This comment has been minimized.

Show comment
Hide comment
@gcazaciuc

gcazaciuc Aug 30, 2016

A new approach that might be of interest on this front is redux-fractal .

gcazaciuc commented Aug 30, 2016

A new approach that might be of interest on this front is redux-fractal .

@lastmjs

This comment has been minimized.

Show comment
Hide comment
@lastmjs

lastmjs Aug 30, 2016

Contributor

This web component allows for separate stores per component: https://github.com/lastmjs/redux-store-element

Multiple components can share the same store by name, and all actions fired from a component are scoped to the store that the component has attached itself to.

<redux-store on-statechange="mapStateToThis" store-name="VIDEO_COMPONENT_STORE"></redux-store>
Contributor

lastmjs commented Aug 30, 2016

This web component allows for separate stores per component: https://github.com/lastmjs/redux-store-element

Multiple components can share the same store by name, and all actions fired from a component are scoped to the store that the component has attached itself to.

<redux-store on-statechange="mapStateToThis" store-name="VIDEO_COMPONENT_STORE"></redux-store>
@kuon

This comment has been minimized.

Show comment
Hide comment
@kuon

kuon Sep 1, 2016

I'm adding this to the discussion: gcazaciuc/redux-fractal#1

It's about accessing parent component state from children.

kuon commented Sep 1, 2016

I'm adding this to the discussion: gcazaciuc/redux-fractal#1

It's about accessing parent component state from children.

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