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

Question: How to choose between Redux's store and React's state? #1287

Closed
tonyzheng121 opened this issue Jan 27, 2016 · 26 comments
Closed

Question: How to choose between Redux's store and React's state? #1287

tonyzheng121 opened this issue Jan 27, 2016 · 26 comments
Labels

Comments

@tonyzheng121
Copy link

No description provided.

@gaearon
Copy link
Contributor

gaearon commented Jan 27, 2016

Use React for ephemeral state that doesn't matter to the app globally and doesn't mutate in complex ways. For example, a toggle in some UI element, a form input state. Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft.

Sometimes you'll want to move from Redux state to React state (when storing something in Redux gets awkward) or the other way around (when more components need to have access to some state that used to be local).

The rule of thumb is: do whatever is less awkward.

@lionng429
Copy link

For data fetched via network requests, always use stores in order to support server-side rendering (if you believe that is the ultimate goal), or you won't be able rehydrate.

For those state to be listened by two or more containers, it should also be in stores?

@xogeny
Copy link

xogeny commented Jan 27, 2016

I agree with @Gaeron about the distinction of ephemeral vs. persistent. But I actually think about this in three categories. One is the UI state that he talks about and I also think of as ephemeral. Another is the application state which is really the persistent core of the application. But a third, which sits in between, is routing state. I use the term "routing" because it is familiar to people, but I abstract this to a "view selection" state since I think that spans desktop, web and mobile better.

Now you could argue that this is UI state because it is deciding what people see (much like the state of tabs, for example). But I see two distinctions. The first is that the state is serialized (i.e., as a URL) and sent to other people. So things should go in the route state if you want people to be able to "deep link" right to that particular UI state. The second is that in many cases the initial route state or a change in route triggers a change in application state (i.e., loading the data to be viewed). Of course, actions in the UI do the same thing. But the distinction I make is that you can (and should) have route state without any view or rendering precisely to test the "application logic" around changes in route state. And it is because the view and rendering don't need to be involved that makes it partly application state, IMHO.

How does this relate to the question of Redux vs. React for state management? Application state is the domain of Redux and UI state is the domain of React. But routing state should (in my opinion) be managed by Redux even though it can be viewed as UI state (see the embedded links for more discussion of why I think that).

To be clear, @gaearon's comment about things moving around still applies. But I just find it useful to distinguish these different cases.

@gaearon
Copy link
Contributor

gaearon commented Jan 27, 2016

Application state is the domain of Redux and UI state is the domain of React.

Note that I don't claim this. I think it's fine both to keep some app state in React, and some UI state in Redux. I don't think they should be separated by domains.

The way I think about it, if you create an app with Redux, embrace the single state tree. Put UI state there as well. However if it gets tedious and frustrating don’t be afraid to put state into the components. My point is that use single state tree unless it is awkward, and only do this when it simplifies things for you rather than complicates them. That’s the only guideline.

@xogeny
Copy link

xogeny commented Jan 27, 2016

First, I certainly didn't mean to put words in @gaearon's mouth, sorry for that.

Second, I completely agree with @gaearon. I also believe firmly in a single state tree approach. When I talked about UI state, I had in my mind really minor things (like what is the current tab selected) where it might not really be that relevant to the application state (exactly has @gaearon discussed).

So let me clarify my position. I agree that pretty much everything should be in Redux, including the route state (as I've done in my TodoMVC implementation with Redux and TypeScript. I specifically mentioned route state and distinguished it because I think (and you can see it in that TodoMVC implementation) that it definitely belongs in Redux (and shouldn't be connected at all to rendering) and is most definitely not "UI state".

For React components I rarely use state. I prefer to use props both to get information about what to render and to get closures that I can invoke to trigger changes external to my component. There are some infrequent circumstances where I introduce state in components just to make life easier, but I try to avoid it. I hope that clarifies things.

@inetfuture
Copy link

I think this question is really subjective and complicated, so I made a tough decision with my team today, don't bother:

  • for non-reusable container, which has connection to Redux, just put everything into Redux store, even tiny UI state like if a modal is open, don't use this.setState any more.
  • for reusable component, which has nothing to do with Redux, use React state.

And now we're implementing some helpers to make it less tedious to manage tiny UI state.

@gaearon @lionng429 @xogeny Any drawbacks you can think of this approach?

@xogeny
Copy link

xogeny commented Jan 28, 2016

@inetfuture I tend to be a bit miserly about application (redux) state because I'm using TypeScript and I define a type for my application state all the way down. That being said, I've gotten in the happen when developing component libraries to separate all the state manipulation from the rendering (see links in previous comments for more details). This means that even for general components that aren't trivial, I completely externalize the state. I generally pass everything in through props (both states and closures for manipulating state). That way, I can easily hook it into my application state. Part of this is undoubtedly an artifact of using TypeScript and not being able to just use combineReducers and connect to hook things in (and preserve the type constraints at least). So this is probably not a representative viewpoint. But I will say that why you say "just put everything into Redux store", I would worry that you'll end up with a kitchen sink of state. I think the fact that my use of TypeScript means that it takes some effort to expand application state is not necessarily a bad thing because it forces me to decide "do I really need this?" instead of letting stuff just pile up.

@markerikson
Copy link
Contributor

As an alternate thought: local component state can be useful for controlled inputs that need a fast update time, as well as reducing the number of actual actions triggered. In my current prototype, I put together a form-editing HOC that receives the current item being edited as a prop. It also handles form input change events by saving the changed field in its state, then calling a debounced "EDIT_ITEM_ATTRIBUTE" action creator with the combined local WIP changes. The result is that the form fields update immediately, because only the form itself is being re-rendered, and a lot fewer actions are triggered (like, if I held down 'A' for a few seconds, only the 'AAAAAAAAAAAA' value would be sent as an action rather than 'A', 'AA', etc).

I've got the HOC up as a gist over at https://gist.github.com/markerikson/554cab15d83fd994dfab , if anyone cares.

Anyway, the point is that that component state and store state both have their uses - you just gotta consider your actual use case.

@sompylasar
Copy link

As for reusable components, if they are large and complex enough, it would be quite future-proof to have their state in Redux, mainly for traceability and replay for testing. But there are still some doubts on how exactly this code reuse should be architected (components plus actions plus reducers).

@galkinrost
Copy link

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

@silvenon
Copy link

I rarely found it awkward to store states in Redux. I like the power it provides, that down the road I can make it interact with some other component if I need it to.

The only thing that’s coming to my mind where it would be awkward is handling the state of form elements, because there can be so many of them. In that case I use a library like redux-form.

@idavollen
Copy link

idavollen commented May 5, 2016

My point is that use single state tree unless it is awkward, and only do this when it simplifies things for you rather than complicates them. That’s the only guideline

@gaearon would you like to share your experiences on how to decide when or how it is considered to be awkward? for exmaple, can you give some typical scenarios/examples you think it's awkward enough if they are put in application state atom? thanks in advance!

@silvenon
Copy link

silvenon commented May 6, 2016

@idavollen I can give a couple:

  • a dropdown open/close state, otherwise you would have to keep track of all the dropdown components in your store
  • when you're debouncing an <input> change, you can use state to update the value instantly (and you're not risking other components to unnecessarily re-render) and update the store in a debounced manner

Basically you should use it for state which doesn't affect other components.

@idavollen
Copy link

I have a third situation to consider, for instance, I have a form (a react container component that has expensive calculation in render method) consisting of many lines with checkbox preceded and a submit button. When the submit button is clicked, the form should submit the lines whose checkboxes are checked, which means transient states of individual checkbox before submitting form when user toggles it is not important and should not be treated as component state and toggling of checkbox should not cause render() to be called.

Fore the time being, I'd neither put states of checkbox in application state atom, nor in local component state, i.e. the form react component in order to avoid un-necessary calling of render() while toggling checkbox, on the contrary, a instance variable is preferred to hold the checked checkboxes, however it's against react-pattern.

I'm wondering what's the best approach of managing these states of checkboxes in this case?

@silvenon
Copy link

silvenon commented May 9, 2016

@idavollen are those checkboxes uncontrolled? If not, you are re-rendering them anyway, so you can keep track of which ones are selected in component's state and use that to select which parts of data will be submitted.

@tiberiumaxim
Copy link

How fast is Redux compared to the components' state? Should I rely on Redux for UI props that are really sensible in terms of timing between the actual event and the rendering process? I have a complicated situation where using components state would require a lot of not so straight forward communication. Any help in this matter would be highly appreciated.

@deowk
Copy link

deowk commented Oct 19, 2016

@tiberiumaxim In my experience (I develop apps in react and redux that run on low powered devices like Smart TV's). In most cases updating component state will be more performant, just because the diff'ing that needs to happen is on a smaller data set and no dispatches need to happen. In cases where only the component or children need access to that state it would be better to stick with local component state and this is often the case with UI related state, if however you need to persist that data into the redux store at some point - you might need to use a combination of local (component) vs global (redux) state and make sure you persist that data to the redux store at the correct time - i.e when a user navigates away from the page - or something to that effect. Maybe if you could shed some more light on your specific case - I could give you more detailed information

@markerikson
Copy link
Contributor

It's up to you to decide what state goes in Redux, and what stays local to a component. You may want to read through http://redux.js.org/docs/faq/OrganizingState.html#organizing-state-only-redux-state and http://redux.js.org/docs/faq/Performance.html#performance-scaling for some related information.

@tiberiumaxim
Copy link

@deowk thanks for sharing your knowledge on the performance matter, I thought the same but needed confirmation.
Also, @markerikson thanks for the links, I'll have a look at them once again.

@sompylasar
Copy link

Not sure if anyone mentioned that explicitly, but local component state can be managed by Redux as well. You can create a store right in your component constructor. This local store would contain the state of this component, and handle actions related to this component (and its children by passing either the dispatch function of this store, or callbacks that call that dispatch function).

This architecture can be implemented manually, or use some library like redux-fractal, redux-ui and so on.

@markerikson
Copy link
Contributor

Or, as Dan has pointed out, you can even implement a reducer-style approach to updating a component's state as well! See https://twitter.com/dan_abramov/status/736310245945933824

@akshay2604
Copy link

I want to know where to store UI state like activity indicator and modal open or close. Does using setState cause problem in unit testing of react components?

@markerikson
Copy link
Contributor

@akshay2604 : again, that's up to you. See the links I posted in previous comments in this thread. You can definitely use setState while testing components, especially if you're using the Enzyme library to help test them.

@Vanuan
Copy link

Vanuan commented Nov 30, 2017

Let me share my thoughts.

I categorize state into 3 broad categories according to how it's related to application's input/output:

  • "cache": a state that is a mirror of persistent data. Example:
{ "todos": [{ id: 1, title: "title" }, { id: 2, title: "title" }] }
  • "changes": a state that describes pending changes to persistent data. Example:
{ "changeTodo": { title: "title", action: "add", done: false },
{ id: 1, title: "changed title", action: "modify", done: true }, { id: 2, action: "delete" } }
  • "view": a state that conveys view options like current filtering, sorting, etc. Example:
{ "displayOptions": { searchTerm: "title", sort: ["title", "desc"], filter: "done=false" } }

You don't mix cache with changes, you don't mix cache with view, you don't mix view with changes.

Of course you need to merge these somehow to display it in a single react component. So that your "merged" object becomes:

{ "todos": [{id: undefined, title: "title", done: false }, { id: 1, title: "changed title" }] }

But that's not a state, it's a result of your application logic. So you shouldn't keep it anywhere.
You can cache this merged state in selectors, you can cache it in the store, you can cache it in components, but it's not a "state", it's not important, you can easily reapply your application logic function to all 3 state pieces and get a merged state again. That's what functional approach is all about.

Redux maintainers will tell you "Hey, you should implement your application logic as multiple reducers and multiple events, which will construct different parts of your "merged" object."
I think that's a wrong approach. It means you won't be able to get away from redux. What was intended to be as a tool to manage your state essentially becomes an implementation of your application logic.

@ghost
Copy link

ghost commented Feb 26, 2018

Hi, I dig into React / Redux for a few days, understand the decision made to choose if some info should be promoted to store or kept in the component local state.

But what if 1 action requires both simultaneously? Here is the use-case, which is very common:

  1. [context] The Redux store is a single leaf { modelItems: myModelItems }. It caches (part of / a projection of) the server model
  2. [context] View is made of a single smart component MyItemsView that displays an UI-list of the stored/cached items. On mounted, component triggers a model-items fetch. Component input: myModelItems. Component output : a fetchModelItems action-trigger. Component internals: state.busy and state.errorId
  3. [plan] When MyItemsView triggers fetchModelItems, an HTTP request is sent to the server in order to fetch the model and cache it in store. This async action has 4 phases and as many hooks: onStarted (HTTP request is about to take-off, loading spinner starts in component), onEnded (HTTP response landed, loading spinner stops in component, one still does not looked at the response status), onFailed (an error notification is shown in component) and onSucceed (the Redux store is updated then the model-cache it holds gets forwarded to the component for rendering)

Therefore, how to split the fetchModelItems action-trigger so that:

  • onStarted, onEnded and onFailed do not hit the Redux store? This because onStarted and onEnded are transient UI states that should have no interaction with the model-cache. The same goes for onFailed (the model-cache remains unchanged and UI will render an error notification). MyItemsView will render UI-state here, not cached-model
  • onSucceed hits the Redux store? This because it won the model-cache lottery, is granted access to the source of truth, will shower and rehydrate, then will render the fresh myModelItems in MyItemsView

About implementation in my current attempt, Redux Thunk takes care of the fetchModelItems action-trigger. I feel like I chose the Redux path there, and that I cannot change my mind anymore to tell "just pass the async success to the Redux store, keep anything else in the React component state".

Puzzled by this, I fetch successes to store, that forwards to the connected component (i.e. Redux container). But no loading spinner and a miserable console-logged error-message atm. I don't want to define store.ui.myItemsView.busy nor store.ui.myItemsView.errorId, but just get those info in the component state.

@markerikson
Copy link
Contributor

@pascalav :
This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux where there are a lot more people ready to help you out - you'll probably get a better answer faster. Thanks!

@reduxjs reduxjs locked as resolved and limited conversation to collaborators Feb 27, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests