Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux -- why is state all in one place, even state that isn't global?

Tags:

I'm new to React and even newer to Redux. So far, having used both together for a small sandbox application, I like them.

When it comes to a larger application, though, I am starting to wonder about this: Why does Redux keep your entire application state in a single store?

If I have an application with many different pieces (and each of those pieces each have their components), it would make sense to me to keep each of those pieces' state to themselves (in each piece's top-level component), as long as their state doesn't affect things with other components.

I'm not sure of the benefits of having the state for everything all in one place, when pieces of that state don't have anything to do with other pieces of it. If component A isn't affected by component B's state, and vice versa, shouldn't their state be kept in their components instead of at the root?

Couldn't I have the globally-affecting state at the root, and the state specific to each component in their own components? I'm concerned about throwing all of the component-specific state all the way up the chain to a global state object (especially when React emphasizes top-down flow).

like image 317
Skitterm Avatar asked Feb 27 '16 01:02

Skitterm


People also ask

Can I use local component state in Redux?

Using local component state is fine. As a developer, it is your job to determine what kinds of state make up your application, and where each piece of state should live. Find a balance that works for you, and go with it. Some common rules of thumb for determining what kind of data should be put into Redux:

What is the root value of a redux state?

The root Redux state value is almost always a plain JS object, with other data nested inside of it. Based on this information, we should now be able to describe the kinds of values we need to have inside our Redux state: First, we need an array of todo item objects.

Is it illegal to mutate the state in Redux?

// ❌ Illegal - by default, this will mutate the state! There are several reasons why you must not mutate state in Redux: It causes bugs, such as the UI not updating properly to show the latest values It makes it harder to understand why and how the state has been updated It breaks the ability to use "time-travel debugging" correctly

What is the use of Redux store?

The store holds the application state. It is highly recommended to keep only one store in any Redux application. You can access the state stored, update the state, and register or unregister listeners via helper methods. Actions performed on the state always return a new state. Thus, the state is very easy and predictable.


2 Answers

The primary advantage of global state for a user-interface application is that you can track state changes of the entire application ATOMICALLY.

tldr; you can always save and predict the app's state easily because there is a single source of truth.

The problem with self-state-managed components is that they create an unpredictable combination of possible states. You cannot easily tell what state your application will be in if XComponent changes itself because you would have to wrangle YComponent and ZComponent and then let them know about each others' state in order for them to make decisions based on that and to determine the state of the overall application.

What this really boils down to is context. If I have a decision that relies on knowing the state of 3 separate parts of the application state that have no direct relationship in terms of their UI composition, how do I get the context to make that decision and communicate the result back to the entire application? Without access to a complete state representation, there is no easy way to get that context aggregation.

Redux (and other patterns like the ratom in Reagent) solve this with global unified state. Your actions are just messengers with state changes but your Store is the context holder. Without a single store, your components would be like Feudal warlords bickering over the state of their loosely related fiefdoms. The Store is the result of a tightly knit oligarchy (combineReducers()) that rule your application state with an iron fist and keeps bugs away :)

Global state works well for UI and solves a lot of problems, even if it's counter-intuitive or even bad practice for other types of software. That said, it's been noted frequently that not ALL of your application state needs to be in the Redux store. Also, you can clear out data that's no longer useful / relevant. Finally, State that is ONLY relevant to a given component (and it's behavior / display) need not be reflected in the global store (necessarily).

The separation-of-concerns abstraction in Redux is the reducer because you can create multiple reducers and combine them to create the logic-chain for store updates.

With reducers, you are still "separating" your state logic in code, but in fact it is all being treated as a single tree when running. This keeps your state changes predictable and atomic, but allows for good organization, encapsulation and separation of concerns.

like image 107
Peter M. Elias Avatar answered Oct 23 '22 01:10

Peter M. Elias


Why do we move our persistent state into a database, rather than letting each of our backend components manage their state in separate files?

Because it makes it much easier to query, debug and serialize our overall application state.

Redux was inspired by a language called Elm, which also promotes the use of a single model. Elm's creator has a further justification on why that's an important quality for applications.

There is a single source of truth. Traditional approaches force you to write a decent amount of custom and error prone code to synchronize state between many different stateful components. (The state of this widget needs to be synced with the application state, which needs to be synced with some other widget, etc.) By placing all of your state in one location, you eliminate an entire class of bugs in which two components get into inconsistent states. We also think you will end up writing much less code. That has been our observation in Elm so far.

I found this concept much easier to learn and understand whilst tackling it from ClojureScript's Re-frame and watching David Nolen's videos on Om Next. The Re-frame README for the project is also a great learning resource.

Here are some of my personal takeaways for why global state is a more elegant solution.

Simpler Components

A common scenario that arises in many stateful component based applications, is a need to modify state that lives in another component.

For example, clicking on an edit button in a NameTag component should open an editor which allows the user to modify some data which lives in the state of a Profile component (a parent of NameTag). The way to solve this problem is to pass down handler callbacks, which then propagate the data back up the component tree. This pattern results in confusing sub-data flows within the existing one way data flow of React applications.

With a global state, components just dispatch actions which trigger updates to that state. Both components and actions can be parameterized to send contextual information back to the user (e.g. what's the id of the user whose name should I am editing).

You don't have to think much about how you're going to communicate changes to the state, because you know exactly where the state is, and actions are the predefined mechanism for sending those changes there.

Pure Functions

When your application state lives in a single location, the components that render it can be pure functions that take state as an argument. They just look at the data, and return a view that can dispatch actions to make updates.

These functions are referentially transparent, meaning that for any given set of inputs, there is always the exact same output. This is ideal for testing and you end up with components that are straightforward to test.

Serialization

In a traditional React application with stateful components, serializing the entire application state would be a nightmare. It would involve traversing the entire component tree and extracting the state value from each component, before collecting them all in a data structure and encoding it.

Re-inflating the components with a serialized state would mean a similar procedure, except you'd also have to figure out which types of components were responsible for which data, so that you could accurately recreate the component tree. This would also mean storing additional info about the component types in your state.

With global state, you can simply encode and decode it exactly where it is.

Undo/Redo

To implement undo/redo with global state, you need to store states in a list, rather than replacing the last one when it updates. An index pointer can control the current state you are at.

With stateful components, this would require each component to implement this mechanism. Not only is this a lot of extra work, but it also creates another point in your application for bugs to be introduced. The best code is no code, as they say.

Action Playback

In Redux (and the Flux pattern in general) we can keep track of the actions that have been played, then play them back in another context to produce exactly the same state.

If you introduce component local state, then you can say goodbye to this. Updates to local state can come from DOM event handlers, network requests, asynchronous operations (and much more). These operations can't be serialized, meaning that they can't be played back either.

like image 37
Dan Prince Avatar answered Oct 23 '22 00:10

Dan Prince