Skip to content
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

What are the disadvantages of storing all your state in a single immutable atom? #1385

Closed
AriaFallah opened this issue Feb 10, 2016 · 91 comments

Comments

@AriaFallah
Copy link

I understand that this is the principle underlying all of redux, and that it comes with all these awesome benefits that are pretty well known by now.

However, I feel that one place that redux is lacking in, is that it doesn't openly describe the conceptual disadvantages of using this architecture. Perhaps this is a good choice since you want people to not shy away due to the negatives.

I'm just curious because it applies not just to redux, but in some capacity to other things like Om, datomic and that talk about turning the database inside out. I like redux, I like Om, I'm interested in datomic, and I really liked that talk.

But for all the searching I've done, it's hard to find people critical of the single immutable store. This is an example, but it just seems to have a problem more with the verbosity of redux than with the actual architecture.

The only things I can think of is that perhaps it takes more memory to keep storing copies of your state, or that it's a little harder to rapidly protoype with redux.

Because you guys have probably put a lot more thought into this than I have, could you help elucidate this for me?

@gaearon
Copy link
Contributor

gaearon commented Feb 10, 2016

That’s a great question. I’ll try to write a comprehensive answer tomorrow but I’d like to hear from some of our users first. It would be nice to summarize this discussion as an official doc page later. We didn’t do this at first because we did not know the drawbacks.

@AriaFallah
Copy link
Author

Thank you very much! I've really been wanting to hear about this.

@gaearon
Copy link
Contributor

gaearon commented Feb 10, 2016

By the way, I encourage everyone who wants to contribute to this discussion to also give an idea of what they were building with Redux so it is easier to understand the scope of the projects.

@jquense
Copy link
Contributor

jquense commented Feb 10, 2016

Its sort of a tough question b/c projects like OM and redux and other single state atom libs, actively mitigate the downsides. Without the the immutability constraint, and gated, controlled access a single state atom is not at all unlike attaching all your data to the window (of which the downsides are well known)

Specific to the Redux approach though, the biggest downside for us is that single state atoms is sort of an all or nothing thing. The benefits of easy hydration, snapshots, time travel, etc only work if there is no other place important state lives. In the context of React this means you need to store state, that properly belongs to components in the Store, or lose a bunch of benefits. If you do want to put everything in Redux it often ends up being burdensome and verbose, and adds an annoy level of indirection.

None of these downsides have been particularly prohibitive for us though :)

@scrapcupcake
Copy link

So, I've been eyeballing Redux, and this style of architecture, to model game states; I have an interest in making Civ style simulated worlds, and fully recorded board game histories more simply, in a browser context, which is perhaps an unusual use case to some, but I doubt anyone would want me to stop, either.

In that context, I'm interested in efforts to manage the size of the history data structure, to move parts of it to the server or disk to save it, restoring specific segments, etc.

The thing I've struggled with, as a new user the most is trying to overcome both the extreme (imho) coding style change in Redux, and the conceptual changes at the same time; it remains a bit daunting even now, a month and change after trying to deep dive into it; I'm looking into https://github.com/ngrx/store next to get at the concepts but with a more familiar style/syntax and more of the rest of the framework implied/provided.

I understand that for some, a framework is a crutch; but some of us need those; few people will be at the top of their game enough to have useful strong opinions, so a frameworks' are better than fumbling in the dark, you know? So that's a point from me, a mid level, middle aged programmer new to the concepts :)

Basically, Redux and your videos taught me how to do it in raw javascript, but being a less experienced programmer, I still have no idea how to deliver a product without the further guidance, so until I see that example of a finished thing, I just kinda stand around like:

Not your fault, but still a problem I'd love to help solve :)

@markerikson
Copy link
Contributor

I think the biggest question I still have at this point is, where should any non-serializable items like functions, instances, or promises go? Was wondering about this over in Reactiflux the other night and didn't get any good responses. Also just saw someone post http://stackoverflow.com/questions/35325195/should-i-store-function-references-in-redux-store .

@timdorr
Copy link
Member

timdorr commented Feb 10, 2016

Use case for multiple stores (keep in mind, this is the best one I could think of):

An app that combines multiple sub-apps together. Think of something like the My Yahoo or another customizable home page product. Each widget will need to maintain its own state without overlap between the two. A product team is likely assigned to each widget, so they might not know the effects of others' code on the environment.

Gathered, you can likely achieve this in Redux by violating a few rules and being careful about propagating those stores on something like React's context. But it might be easier with a framework that handles multiple state atoms by default.

@chicoxyzzy
Copy link
Contributor

@gaearon we are building trading platform. I've already explained to you once why one store doesn't fit for us (after React.js meetup in Saint-Petersburg). I'll try to explain it again in details (in blog post on medium?) but I probably need help due to my English skills :) Is it ok if I'll send it to you or someone else here for review in Twitter direct messages when it will be done? I can't name the exact date though but I'll try to write this post soon-ish (most likely end of this month).

@chicoxyzzy
Copy link
Contributor

And yes we are still using Redux with multiple stores by violating some rules from docs as @timdorr said (in cost of that we can't use react-redux as comfortably in cases were we need data from various stores as it is in case of single store)

@slorber
Copy link
Contributor

slorber commented Feb 10, 2016

Redux is basically event-sourcing where there is a single projection to consume.

In distributed systems, there is generally one event log (like Kafka), and multiple consumers that can project/reduce this log into multiple databases/stores that are hosted on different servers (typically, a DB replica is actually a reducer). So in a distributed world the load and memory usage is... distributed, while in Redux if you have hundreds of widgets that all have their local state, all this run in a single browser, and every state change has some overhead due to immutable data updates.

In most cases this overhead is not a big deal, however when mounting text inputs into a not so good state shape, and typing fast on a slow mobile device, it's not always performant enough.

Rendering that state from the very top is, in my experience, is not convenient and also not always performant enough (at least with React which is probably not the fastest VDom implementation). Redux solves this with connect but still, as the number of connection grows, it can become a bottleneck. There are solutions

Also persistent data structures like ImmutableJS are not always offering the best performances on some operations, like adding an item at a random index in a big list (see my experience rendering big lists here)

Redux includes both the event log and the projection because it is convenient and is ok for most usecases, but it could be possible to maintain an event-log outside redux, and project it into 2 or more redux stores, add all these redux stores to react context under different keys, and have less overhead by specifying which store we want to connect to. This is absolutly possible, however this would make api and devtools harder to implement as now you need a clear distinction bettween the store and the event log.


I also agree with @jquense

The benefits of easy hydration, snapshots, time travel, etc only work if there is no other place important state lives. In the context of React this means you need to store state, that properly belongs to components in the Store, or lose a bunch of benefits. If you do want to put everything in Redux it often ends up being burdensome and verbose, and adds an annoy level of indirection.

Mounting any state to Redux store requires more boilerplate. Elm architecture probably solves this in a more elegant way but also requires a lot of boilerplate.

But it is also not possible or performant to make all state controlled. Sometimes we use existing libraries for which it is hard to build a declarative interface. Some state is also hard to mount to redux store in an efficient way, including:

  • Text inputs
  • Scroll position
  • Viewport size
  • Mouse position
  • Caret position / selection
  • Canvas dataUrls
  • Unserializable state
  • ...

@AriaFallah
Copy link
Author

This question I just saw on SO:

http://stackoverflow.com/questions/35328056/react-redux-should-all-component-states-be-kept-in-redux-store

Echos some confusion I've seen about managing UI state, and whether the UI state should belong to the component or go into the store. #1287 offers a good answer to this question, but it's not so initially apparent and can be up for debate.

Additionally, this could be a hindrance if you're trying to implement something like this https://github.com/ericelliott/react-pure-component-starter in which every component is pure and does not have a say in its own state.

@taggartbg
Copy link

I found it difficult to use redux's single state tree when I needed to manage multiple sessions' data. I had an arbitrary number of branches with an identical shape and each had multiple unique identifiers (depending on where the data came from, you'd use a different key). The simplicity of the reducer functions and reselect functions quickly disappeared when faced with those requirements - having to write custom getters / setters to target a specific branch felt overly complex and flat in an otherwise simple and compossible environment. I don't know if multiple stores is the best option to address that requirement, but some tooling around managing sessions (or any other identically-shaped data) into a single state tree would be nice.

@istarkov
Copy link
Contributor

Increased probability of state keys collisions between reducers.
Data mutations and declarations are far from the code where the data is used (when we write a code we try to place variable declarations near the place where we use them)

@jsonnull
Copy link

Let me preface with my particular use-case: I'm using Redux with virtual-dom, where all of my UI components are purely functional and there's no way to have local state for various components.

With that said, definitely the hardest part is tying in animation state. The following examples are with animation state in mind, but much of this can be generalized to UI state.

Some reasons why animation state is awkward for Redux:

  • Animation data changes frequently
  • Animation-specific actions pollute the history of actions in your app, making it hard to roll-back actions in a meaningful way
  • Redux state tree starts to mirror component tree if there's many component-specific animations
  • Even basic animation state like animationStarted or animationStopped starts to couple state to your UI.

On the other hand, there are definitely challenges to building the animation state entirely outside of the Redux state tree.

If you try to do animations yourself by manipulating the DOM, you have to watch out for stuff like this:

  • virtual-dom diffs will not take into account your animation manipulation, such as custom styles you set on the node—if you set some styles on a DOM node, you have to remove them yourself if you want them to go away
  • If you perform animations on the document itself, you have to be painfully aware of virtual-dom updates—You may start an animation on one DOM node, only to find that the content of that DOM node was changed mid-animation
  • If you perform animations on the document itself, you have to be wary of virtual-dom overwriting styles and attributes you set on your node, and you have to be wary of overwriting styles and attributes set by virtual-dom

And if you try to let virtual-dom take care of all the DOM manipulation (which you should!) but without keeping animation state in Redux, you end up with these tough problems like these:

  • How do you expose animation state to your components? Local state in your components? Some other global state?
  • Normally your state logic is in Redux reducers, but now you have to add a lot of rendering logic directly into your components for how they will animate in response to your state. This can be quite challenging and verbose.

There are currently awesome projects like react-motion that make great strides forward in solving this problem for React, but Redux is not exclusive to React. Nor should it be, I feel—a lot of folks are bringing their own view layer and trying to integrate with Redux.

For anybody who's curious, the best solution I've found for Redux + virtual-dom is to keep two state atoms: Redux keeps the core application state, holds the core logic to manipulate that state in response to actions, etc. The other state is a mutable object that holds animation state (I use mobservable). The trick then is to subscribe to both Redux state changes and animation state changes, and to render the UI as a function of both:

/* Patch h for jsx/vdom to convert <App /> to App() */
import h from './h'
import { diff, patch, create } from 'virtual-dom'
import { createStore } from 'redux'
import { observable, autorun } from 'mobservable'
import TWEEN from 'tween.js'
import rootReducer from './reducers'
import { addCard } from './actions'
import App from './containers/App'

// Redux state
const store = createStore()

// Create vdom tree
let tree = render(store.getState())
let rootNode = create(tree)
document.body.appendChild(rootNode)

// Animation observable
let animationState = observable({
  opacity: 0
})

// Update document when Redux state 
store.subscribe(function () {
  // ... anything you need to do in response to Redux state changes
  update()
})

// Update document when animation state changes
autorun(update)

// Perform document update with current state
function update () {
  const state = store.getState()
  let newTree = render(state, animationState)
  let patches = diff(tree, newTree)
  rootNode = patch(rootNode, patches)
  tree = newTree
}

// UI is a function of current state (and animation!)
function render (state, animation = {}) {
  return (
    <App {...state} animation={animationState} />
  )
}

// Do some animations
function animationLoop (time) {
  window.requestAnimationFrame(animationLoop)
  TWEEN.update(time)
}
animationLoop()

new TWEEN.Tween(animationState)
      .to({ opacity: 100 }, 300)
      .start()

// Or when you dispatch an action, also kick off some animation changes...
store.dispatch(addCard())
/* etc... */

@gaearon
Copy link
Contributor

gaearon commented Feb 11, 2016

Thank you all for great responses! Keep them coming.

One thing to note is that we don’t intend Redux to be used for all state. Just whatever seems significant to the app. I would argue inputs and animation state should be handled by React (or another ephemeral state abstraction). Redux works better for things like fetched data and locally modified models.

@gaearon
Copy link
Contributor

gaearon commented Feb 11, 2016

@taggartbg

I found it difficult to use redux's single state tree when I needed to manage multiple sessions' data. I had an arbitrary number of branches with an identical shape and each had multiple unique identifiers (depending on where the data came from, you'd use a different key). The simplicity of the reducer functions and reselect functions quickly disappeared when faced with those requirements - having to write custom getters / setters to target a specific branch felt overly complex and flat in an otherwise simple and compossible environment.

Would you mind creating an issue describing your use case in more detail? It might be that there is a simple way to organize the state shape differently that you are missing. In general multiple branches with same state shape but managed by different reducers is an anti-pattern.

@gaearon
Copy link
Contributor

gaearon commented Feb 11, 2016

@istarkov

Increased probability of state keys collisions between reducers.

Would you mind explaining how this happens in more detail? Normally we suggest you to only run a single reducer on any state slice. How can collisions happen? Are you doing multiple passes over the state? If so, why?

@markerikson
Copy link
Contributor

@gaearon @istarkov : perhaps what's meant is that various plugins and related libraries might be jostling for the same top-level namespace? Library A wants a top key named "myState", but so does Library B, etc.

@gaearon
Copy link
Contributor

gaearon commented Feb 11, 2016

Yeah, this is a good point even if this is not what @istarkov meant. (I don’t know.) In general libraries should offer a way to mount reducer anywhere in the tree but I know that some libraries don’t allow that.

@taggartbg
Copy link

@gaearon

Would you mind creating an issue describing your use case in more detail? It might be that there is a simple way to organize the state shape differently that you are missing. In general multiple branches with same state shape but managed by different reducers is an anti-pattern.

I'd be happy to! I'll do that when I get a moment.

I think its definitely regarded as an antipattern, although I have a single, shared, reducer to manage those branches. Nonetheless, I feel like the paradigm that redux provides is not far from being a perfect use case for serializing / deserializing identical branches as immutable state. I don't see why it would be against the ethos of redux to build something like reselect with some assumed logic for targeting specific branches by some key.

I'd be happy to chat about it off-thread, I could very well be wrong.

@markerikson
Copy link
Contributor

@taggartbg : speaking entirely without knowing what your code looks like here. Are you talking about trying to deal with state that looks like this?

{ groupedData : { first : {a : 1, b : 2}, second : {a : 3, b : 4}, third : {a : 5, b, 6} }

Seems like you could deal with that on the reducer side by having a single reducer function that takes the per-group ID key as part of each action. In fact, have you looked at something like https://github.com/erikras/multireducer, https://github.com/lapanoid/redux-delegator, https://github.com/reducks/redux-multiplex, or https://github.com/dexbol/redux-register?

Also, on the reselect side, the fact that React-Redux now supports per-component selectors might help, since that improves scenarios where you're doing selection based on component props.

@jsonnull
Copy link

@gaearon

One thing to note is that we don’t intend Redux to be used for all state. Just whatever seems significant to the app. I would argue inputs and animation state should be handled by React (or another ephemeral state abstraction). Redux works better for things like fetched data and locally modified models.

A React developer reading the docs and tutorial already has this ephemeral state abstraction at their disposal, so it's possible this is already in mind when considering where Redux makes sense for a project.

However, from the perspective of a developer with little or no experience with React that wants to take a functional approach to UI, it may not be obvious which state belongs in Redux. I've done several projects now where at first Redux and virtual-dom were sufficient, but as the application grew in complexity this other "ephemeral state abstraction" became necessary. This isn't always apparent the first time you add in an animation reducer with some basic animation flags, but later on becomes quite a pain.

It might be nice for the docs to mention which state is right for Redux and which state is better solved by other tooling. It may be redundant for React developers, but could be very beneficial for other devs to have in mind when looking at Redux and planning their application architecture.

EDIT: the title of the issue is "What are the disadvantages of storing all your state in a single immutable atom?" This "all your state in a single immutable atom" is exactly the kind of wording that ends up getting a lot of the wrong kind of state in the Redux tree. The documentation could provide some explicit examples to help developers avoid this kind of trap.

@Cmdv
Copy link

Cmdv commented Feb 11, 2016

@gaearon We had an interesting conversation over on Cycle.js about the architectural differences between a single atom state vs pipping. HERE.

I know this isn't 100% Redux related but wanted to give a different perspective from an implementation that uses Observable streams as a data-flow around an app (for Cycle the app being a set of pure functions).

Because everything in Cycle is an Observable, I was having difficulties getting state to move from one route change to another. This was due to the state Observable not having a subscriber once a route was changed and thought why not implement a single atom state, so any time state changed in my app it would report back to the top level store, bypassing any piping (view terrible drawings here).

With Cycle because side effects happen in Drivers you usually have to make that loop from top level driver to the component and back, so I wanted to just skip that and go straight back up and out to the components listening. Was taking Redux as a source of inspiration.

It's not a case that either is right or wrong but now I do piping and we have figured out a state driver, having the ability to pipe my state to different components as I need to is really flexible for refactoring, prototyping, and having strong understanding each source of state, each consumer and how they got to each other.

Also newcomers to the application (if they understand Cycle of course), can easily connect the dots and very quickly draw a visual representation in there mind of how state is handled and piped and all this from the code.

Sorry in advance if this is totally out of context, wanted to demonstrate how a different paradigm had a similar conversation 😃

@chicoxyzzy
Copy link
Contributor

@timdorr

Each widget will need to maintain its own state without overlap between the two. A product team is likely assigned to each widget, so they might not know the effects of others' code on the environment.

I think it's better when widget has it's own state (maybe even it's own (redux?) store) so it can work standalone in any (mashup-)app and receive only some of properties it. Think about weather widget. It can fetch and show data by itself and receive only properties like city, height and width. Another example is Twitter widget.

@sompylasar
Copy link

@chicoxyzzy Related: reduxjs/react-redux#278

@elado
Copy link

elado commented Feb 16, 2016

Great discussion!

Mainly, I find it inconvenient to combine multiple apps/components/plugins.

I can't just take a module I build and throw it in another module/app, as I'll need to separately import its reducers into the store of the app, and I'll have to put it under a specific key that my module knows about. This also limits me from duplicating the module if I use @connect, because it connects to the entire state.

For example:

I'm building a messenger that looks like iMessage. It has a redux state of currentConversationId etc. My Messenger component has @connect(state => ({ currentConversationId: state.messenger.currentConversationId })).

I want to include this Messenger in a new app. I'll have to import { rootReducer as messengerRootReducer } from 'my-messenger' and add it to combineReducers({ messenger: messengerRootReducer }) for it to work.

Now, if I want to have two <Messenger> instances in the app that have different data, I can't, because I'm bound to state.messenger in the Messenger component. Making work against a specific slice of the store will make me use a customized @connect.

Also, let's say the containing app has a certain action name that exists on the my-messenger module, there's gonna be a collision. This is why most redux plugins have prefixes like @@router/UPDATE_LOCATION.

A few random/crazy ideas:

1

@connect can connect to a specific slice of the store, so I can include in my app multiple <Messenger>s that connect to their own slice of data, such as state.messenger[0]/state.messenger[1]. It should be transparent to <Messenger> which still keeps connecting to state.messenger, however, the containing app can provide the slice by something like:

@connect(state => ({ messengers: state.messengers }))
class App extends Component {
  render() {
    return (
      <div>
        {this.props.messengers.map(messenger =>
          <ProvideSlice slice={{messenger: messenger}}><Messenger /></ProvideSlice>
        }
      </div>
    )
  }
}

There's a problem when using global normalized entities, state.entities will also need to be present, so along with the sliced state, the entire state needs to be present somehow. ProvideSlice can set some context that Messenger's @connect will be able to read from.

Another issue is how to bind actions fired by components in <Messenger> affect only that slice. Maybe have @connect under ProvideSlice run mapDispatchToProps actions only on that slice of the state.

2

Combine store and reducer definitions, so each reducer can be also standalone. Store sounds like a controller while a reducer is a view. If we take react's approach, "everything is a component" (unlike angular 1.x's controller/directive), it may allow components and their state/actions truly be standalone. For things like normalized entities (i.e 'global state') something like React's context can be used, and it can be accessible through all actions (e.g. getState in thunk)/connected components.

@stephenbunch
Copy link

For what it's worth, I had an idea of creating "branches". It totally defies the single store principle, but it seemed like a good compromise.

https://github.com/stephenbunch/redux-branch

@jsonnull
Copy link

@jedwards1211 I think that as apps grow in complexity there's eventually the need to break out certain types of data and certain types of updates so that certain stores aren't handling the full load.

Let's say that there's some core state that you want sync'd with the backend, there's some animation state so that your app UI components are displaying correctly according to the current actions being performed, and maybe you have some debug data that you're tracking separately.

In this contrived example, it's certainly possibly to build all of this out of a single Redux store, but depending on how you architect it, there'll be some blurry lines between concerns of various branches of the state tree, and maybe lack of clarity regarding which state a given action is intended to interact with.

I think it's perfectly reasonable to use different state management strategies according to how you want state to be scoped and what performance characteristics you expect out of it. Redux is suitably geared towards state in which you may want to rollback or replay actions, or serialize and return to later. For animation state, you can use a mutable store or a reactive store if you want—there will be less overhead that way and you won't be polluting your application state with this transient (but still necessary) interaction state.

Real quick, I want to make a point using the upcoming React fiber architecture. Here's a link, apologies if my understanding is a bit out of date. Roughly, the fiber architecture acknowledges that there are different kind of updates that will propagate through a React component tree, and there's different expectations regarding when and how these updates will perform. You want animation updates to be fast, responsive, and reliable, and you don't want big interaction updates to introduce jank in your animations and such.

So the fiber architecture breaks down updates into work packets and schedules them according to a priority based on the work being performed. Animations are high priority—so that they're not interrupted, and slower mechanical updates have a lower priority.

I've skipped over a lot of details about React fibers and probably gotten some things wrong in the process, but my point is that this is the kind of granular approach I think is necessary for your data store with different types of data.

If I was building a complex app today, I'd start with a Redux-like top-level store class backed by a few different stores. Roughly:

  • Top level data store has a queue for incoming actions
  • Actions are either for animation actions that should get dispatched quickly, or application interactions that have lower priority
  • Animation actions in the queue get dispatched to an observable backend from a requestAnimationFrame loop
  • Interaction actions get dispatched to redux when the animation actions from the queue are complete

This story seems pretty close to a complete state solution based on Redux. Redux is suited to data you want to view after a sequence of actions, and there's a lot of other state out there that needs careful handling as well.

@jedwards1211
Copy link

@jsonnull I've never had any need to store animation state in a store -- local state has always been ideal for my animation use cases. I'd be curious to know if there are some use cases that local animation state is completely unsuited for. It sounds like they have great ideas for the new architecture though.

@jsonnull
Copy link

jsonnull commented Oct 16, 2016

@jedwards1211 There's a couple cases I can think of where local animation state doesn't work. I'll grant you, they're not cases you'll run into with every application, but I think they come up often enough.

In one case you're using a library other than Redux where you don't have local state. (Ok, yes, I know I'm cheating a little here.) If you're using a very lean hyperscript approach, no virtual dom, pure functional components, then instead of local state you're going to have to pass some animation state to the root node or whichever node you're re-rendering.

In this case, having a state tree with a few animation details set will allow you to work around lack of local state so that you can trigger animations, have the animations run for a duration, and more.

The key thing here is that there's two ways to do these animations—do them as part of your render and keep the "UI as a function of state" metaphor intact, or mutate the DOM separately. Almost always the better answer is to just re-render instead of mutating.


Now for an example where you do have the ability to keep some animation state local—building a browser game with a React-based UI on top of it.

  • Usually you'll drive the game using ticks... the game time starts at 0 and is incremented every frame. Core game animations may be done on canvas or in WebGL where there's no local state, so you're going to base animation start time and progress based off of these ticks.

    Ex: A sprite character has an animation comprising of 10 frames, and you want to play out the animation over ~400ms, so you're going to change the sprite drawn every 2 ticks or so.

    Your ticks could also be timestamps if you want higher resolution.

  • Your game can also pause, in which case you may want to halt some animations done in the React UI side.

In this game example, what you don't want to do is increment your ticks as part of a tick action... instead you want to increment ticks separately, possibly in an observable structure. And when you dispatch Redux actions such as keyboard character movement or attack, you'll pass the current tick or current time to them, so that you can record which tick an action occurred at and your UI components can record locally what time an animation began.

Now you have separated the global animation state from the interaction state, and if you want to do a "replay" using only the Redux state or by replaying actions, you can, and you're not burdened by incrementing ticks as part of your state tree.

Generally speaking, it's also much better to coordinate animations by passing start/stop times through Redux and letting components maintain the rest of the state locally.

This does not only apply to games. Any extended sequence of animations might go through this, such as if you wanted to do a series of long-running animations on a site using React.

@jedwards1211
Copy link

@jsonnull cool, that's a great example, thanks for sharing that.

@ghost
Copy link

ghost commented Nov 9, 2016

I would simply say - in Redux you just can't store you state objects with links to each other. For example - user can have many posts and post can have many comments and I want to store those objects in a way:

var user = {id: 'user1', posts: [], comments: []}
var post = {id: 'post1', user: user, comments: []}
user.posts.push(post);
var comment = {id: 'comment1', post: post, user: user}
post.coments.push(comment)
user.comments.push(comment)
appState.user = user

Redux doesn't allow usage of object hierarchies with circular references. In Mobx (or Cellx) you just can simply have one-to-many and many-to-many references with objects and it simplified business logic many times

@jedwards1211
Copy link

jedwards1211 commented Nov 9, 2016

@bgnorlov I don't think anything in the redux package prohibits circular references like you're talking about -- they just make it more difficult to clone your state in your reducer. Also, it might be easier to make changes to state with circular references if you use Immutable.js to represent your state, though I'm not sure.

You could also model relationships in your redux state by using foreign keys, though I understand this is not as convenient as using ORM-like object references:

var user = {id: 'user1', postIds: [], commentIds: []}
var post = {id: 'post1', userId: user.id, commentIds: []}
user.postIds.push(post.id);
var comment = {id: 'comment1', postId: post.id, userId: user.id}
post.commentIds.push(comment.id)
user.commentIds.push(comment.id)
appState.userId = user.id
appState.posts = {[post.id]: post}
appState.comments = {[comment.id]: comment}

// then join things like so:
var postsWithComments = _.map(appState.posts, post => ({
  ...post,
  comments: post.commentIds.map(id => appState.comments[id]),
})

@ghost
Copy link

ghost commented Nov 9, 2016

@jedwards1211 to clone state with circular references in redux, reducer must return every new copy of an object which affected by changes. If reference to object changes the related object also need to be a new copy and this will be repeated in а recursive way and it will generate whole new state on every change. Immutable.js can't store circular references.
With normalization approach when some event handler needs some data it requires every time to take object by its id from global state. For example - I need to filter task siblings somewhere in event handler (tasks can have hierarchical structure) With redux I need to dispatch thunk to get access to app state

var prevTask = dispatch((_, getState)=>getState().tables.tasks[task.parentId]).children.map(childId=>dispatch((_, getState)=>getState().tables.tasks[childId])).filter(task=>...) [0]

or with selectors

var prevTask = dispatch(getTaskById(task.parentId)).children.map(childId=>dispatch(getTaskById(childId)).filter(task=>...)[0]

and this boilerplate turns code into mess compare to mobx version when I can simply write

var prevTask = task.parent.children.filter(task=>...)[0]

@markerikson
Copy link
Contributor

@jedwards1211 , @bgnorlov : FWIW, this is one of the reasons why I like Redux-ORM. It allows you to keep your Redux store normalized, but makes doing those relational updates and lookups simpler. In fact, that last example is basically identical with Redux-ORM.

I just wrote a couple blog posts describing Redux-ORM basics, as well as core concepts and advanced usage.

@jedwards1211
Copy link

@markerikson cool, thanks for the tip!

@leff
Copy link

leff commented Mar 30, 2017

@gaearon

One thing to note is that we don’t intend Redux to be used for all state. Just whatever seems significant to the app. I would argue inputs and animation state should be handled by React (or another ephemeral state abstraction). Redux works better for things like fetched data and locally modified models.

I found this comment to be so incredibly useful. I know I'm late to the party, but maybe that's part of my point. Over a year since that comment was posted and this is still the only place I've seen this idea expressed.

Coming from an angular and decidedly non-flux/redux background it is very hard to formulate that idea on your own. Especially when a lot of examples still create actions for every keyup in a text box. I wish somebody would put that quote in 50px text on the top of every Redux documentation page.

@jwhitley
Copy link

@leff Agreed. I'd synthesized that exact idea some time back, but it doesn't get called out enough. Even if you're using Redux for history management, there's often a lot of ephemeral state that doesn't need to burden your Redux-style state management.

That won't necessarily be a surprise to folks who've had the opportunity to work on, e.g. a mature desktop app that does rich undo/redo. But to the oceans of newcomers coming to these ideas via Redux, it'll be a revelation.

@jedwards1211
Copy link

@bgnorlov that's a good point that it's impossible to store circular references with Immutable.js, I never thought about that! I think it's a nice feature though.

@jedwards1211
Copy link

jedwards1211 commented Mar 30, 2017

These days we do tend to store almost everything in Redux, even if it's transient view-specific state that we never use outside its context (e.g. state for a redux-form instance). In most cases we could move such state out of redux without suffering any problems. But the upside is it's there in case we ever need external components to respond to it in the future. For instance, we could have some icon in the navbar that changes when any form has errors or is submitting.

I'd say the most important thing to keep in mind is that putting all normalized state in Redux (i.e. anything that needs to be joined together to render views) will make it easiest to use. State that doesn't need to be joined with anything can probably live outside of Redux without causing you difficulty, but it usually doesn't hurt to put it in Redux either.

@markerikson
Copy link
Contributor

FWIW, the docs do point out that not everything has to go into Redux, per http://redux.js.org/docs/faq/OrganizingState.html#organizing-state-only-redux-state .

There's been quite a bit of recent chatter online about what Redux is "appropriate for". Some people see benefits from putting literally everything into Redux, others find that to be too much hassle and only want to store data retrieved from a server. So, there's definitely no fixed rule here.

As always, if people have ideas for improving the docs, PRs are totally welcome :)

@eloytoro
Copy link

If you ever need to reuse reducers and be able to allocate "sub-states" in your redux state I developed a plugin to do it effortlessly (with React integration)

https://github.com/eloytoro/react-redux-uuid

@jedwards1211
Copy link

@eloytoro IMO if you have trouble with some 3rd-party reducer having hardcoded action types or location in the state, you should open an issue in the project...before long it will become unacceptable design practice as people learn the reusuable reducer/action creator pattern.

@avesus
Copy link

avesus commented Mar 31, 2017

@eloytoro I was going to write something like that. Thank you very much for the reference!

@eloytoro
Copy link

eloytoro commented Apr 1, 2017

@jedwards1211 I think you got the wrong idea. I haven't mentioned 3rd party reducers or issues with them in any way, just trying to showcase how my snippet can solve the issue for making reducers reusable in collections with dynamic sizes

@avesus hope it works for you

@jedwards1211
Copy link

@eloytoro ah, that makes more sense.

@papiot
Copy link

papiot commented Apr 19, 2017

Last year I created a fairly complex game using Redux. I have also participated in another project which doesn't use Redux, but instead a custom-built, minimalistic framework which separates everything into UI Stores and Server Stores (including inputs). Without going into too many details, my conclusions so far are the following:

Single state is always better, however, do not over do it. Getting every keyDown() through the store and back to the view is just confusing and unnecessary. So transient states should be handled by local components states (such as React's).

@mib32
Copy link

mib32 commented Jun 12, 2017

I think that since the redux and react inflamed the observable web, the quantity of good animation on the pages declined.

@jedwards1211
Copy link

@mib32 you may be right! Hopefully people will eventually get used to crafting good animation in React.

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

No branches or pull requests