Redux actions/reducers vs. directly setting state - reactjs

I'm new to Redux. I'm having trouble understanding the value of actions and reducers vs. components directly modifying the store.
In Redux, your React components don't change the store directly. Instead they dispatch an action -- sort of like publishing a message. Then a reducer handles the action -- sort of like a message subscriber -- and changes the state (more precisely, creates a new state) in response.
I feel like the pub/sub-like interaction adds layers of indirection that make it harder to understand what a component is actually doing -- why not just allow components to pass new state to the Redux store directly? Would it be a bad thing to inject something like this.props.setReduxState into a React component?
I'm starting to understand the value of why the state itself needs to be immutable (related question -- Isn't Redux just glorified global state?), related to checking for updates to see which component props need to be updated in response to state changes. My question is the extra action/reducer layers vs. manipulating the store directly.

I had some very similar questions when I decided on going down the Redux rabbit hole. In my case I was developing relatively small apps and components using just the React internal state, which still works really well. It was only when the app and the number of components grew much larger that setState became somewhat unwieldy.
I don't think this is emphasized enough - you don't always have to use Redux if you're working on a relatively small app or a few components that are easy enough to keep track of without a robust immutable store solution. There is a lot of necessary boilerplate just to get started, which can be overly time consuming in some instances.
That being said, React + Redux is a great design paradigm to follow once you are used to it and have your own boilerplate you can call upon. Redux essentially favors props over state, which forces immutable design as you (generally) can't mutate props. This is what makes the Redux DevTools "time machine" possible, along with all of the middleware that you can throw at your store as previously mentioned by others.
Middleware is a big part of it, especially with the advent of more precise solutions for sync and async tasks alike like redux-saga (https://redux-saga.js.org/). IMO "thunks" (https://github.com/gaearon/redux-thunk) are easier to grok than sagas when you're just getting started, unless you're already an expert with generators, but sagas are much more predictable and therefore testable in the long run.
So instead of having a separate internal state for each component, you can have a set of actions / reducers that you use instead, knowing that you can't mistakenly mutate the state. I've found the combination of ImmutableJS (https://facebook.github.io/immutable-js/) and Reselect (https://github.com/reactjs/reselect), which gives you composable selectors - to be great for this. No need for Object.assigns or tons of spread operators, it creates a new object for you.
I wouldn't be in a hurry to convert existing non-Redux projects into Redux, the workflow is different enough to cause significant confusion, but you'd be hard pressed to find boilerplates for new projects that don't already have Redux in their workflow. Such as "React Boilerplate" (https://github.com/react-boilerplate/react-boilerplate), I know this kind of blew my mind at first, but definitely worth getting a feel for. It really tests your functional programming chops.

In the development process, you often need to know who and how has changed the state. Mutating state by emitting actions allows you to keep answers to those questions.
Actions are payloads of information that tell the store how it should be modified. This information is represented in form of plain javascript objects which allows this information to be logged, serialized and stored. Since all the history is "remembered", you can later replay all the chain of actions for debugging or testing purposes. Together with a tool like Redux DevTools it makes development process really easy and amazing. Since all the store modifications are logged to a monitor, so you can see how and when was the state modified on every step. Even more, you can go back or forward through the actions chain.
Another benefit of having all mutations centralized in one place is the fact that it's easier to take control over the state. That guarantees, that all mutations happen one by one in a strict order and no callbacks can make the application behavior unstable. It also allows to keep in one place functionality that is common for some actions, or in other words to apply middlewares.

Related

React Context performance and suggestions

This is a phrase you can find in many sites and it is considered (?) as valid:
React Context is often used to avoid prop drilling, however it's known
that there's a performance issue. When a context value is changed, all components
that use useContext will re-render.
Moreover:
The React team has proposed a useSelectedContext hook to prevent performance issues
with Context at scale. There is a community library for that: use-context-selector
However, for me the above does not make any sense. Don't we want to re-render all the components that use useContext? Absolutely! Once the context value changes, all components using it must re-render. Otherwise, UI won't be in sync with the state. So, what exactly is the performance issue?
We could discuss how not to re-render the other child components of the Context Provider that do not use useContext and this is achievable (react docs):
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
By using the above pattern, we can avoid re-rendering of all child components that do not use useContext.
To recap: In my opinion, there is no performance issue when using Context the right way. The only components that will re-render are the ones that should re-render. However, almost all references insist on an underlying performance issue and stress that as one of Context's caveats. Am I missing something?
Note: I'll answer your questions and give a final comment on what I could gather on the internet and from personal experience.
TL;DR: The performance issue is a constant reminder that when you use Context API even if not explicitly writing it you are virtually passing those props to the components that use a Context State and on each state change for every component that state is accessed, those components will be re-rendered.
Answering each question from your post:
Question 1: Don't we want to re-render all the components that use useContext?
Yes, that's exactly it, as you stated.
Question 2: So, what exactly is the performance issue?
On prop changes, components re-render. When using Context API, even if not explicitly passing the Context state as a prop, each state change will trigger a re-render on that component and the child components that depend on or receive that state as a prop. You can read this on this doc such as:
Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree
This is not exactly an issue as the docs suggest you use it to store global state that does not change that much such as:
Theme
Authentication/Login state
Language/i18n
Those are the type of data that:
if changed, should trigger a "global" re-render as it'll affect the App, as a whole
do not change that much (or do not change at all) on each App interaction
will be used on different nesting levels
Question 3: Am I missing something?
Well, as you could already suppose, there are a lot of cases that don't match that type of usage of a "global state" that doesn't change that much. And, for those, there are some other options that could be used to take care of the same set of cases that Context API solve, but with a lot more overhead at the code. One of them is Redux, which doesn't have this overhead for the most obvious reason: Redux creates a parallel store from your App and doesn't pass the values as props to each and every component. On the other hand, one of the most noticeable overheads is the tooling that the project should have to accommodate that library.
But why people started using Redux(and other libs) in the first place?
Handling Global State in past versions of React was a thing. You could solve this with a lot of different ways, opinions, and approaches. Sooner or later, people started to create and use tools and libs that handles that with approaches that were considered "better" for specific or personal reasons.
Later, those tools/libs started to get more complex and with more possible "connectors" or "middlewares" here and there. As an example, one tool that can be added to Redux to handle requests is the lib called Redux Thunk, which allows performing requests inside actions (if I'm not wrong) breaking the concept of just writing actions as pure functions from Redux. Nowadays, with the growth of React Query/TanStack Query, the state related to the requests is starting to be handled also as a parallel "global state" even with cache and a lot more features, dropping the usage of Redux Thunk and Redux as a consequence to solve the "global state" from requests.
After the release of Context API to a stable and improved version, people started to use it for a lot of projects and as a Global State manager. And sooner or later everyone started to notice performance issues related to too many re-renders due to several props changing every time, everywhere. Some of them just went back to the Redux and other libs but for others, turns out that Context API is very good, practical, involves less overhead, and is embedded with React if used as it was intended. The requirements for not having to deal with the performance issues are the same as described before:
a global state that does not change so frequently and
will be used on different nesting levels
There are some other options that Context API works smoothly if you don't nest too many components. As an example: Multi-page Forms if you create the context at the Route level and not at the App level. As you also stated:
In my opinion, there is no performance issue when using Context the right way
But you could say that for almost every tool that is used out of its original usage conception.
Edit: After the OP pointed out and after reading about the Context API in the official docs, props are not passed to each and every child, just for those who use the context. And as a consequence: To each and every child component for those components that pass those props to them.
And, answering the question on why Context API has performance issues, I'm planning to create a repo to reproduce and understand but my bet is: It is probably related to the fact that each Context is called as "component" and React handles that itself, instead of creating a "parallel structure" such as Redux/Jotai, as an example.
To recap: In my opinion, there is no performance issue when using Context the right way. The only components that will re-render are the ones that should re-render.
If your Provider only provides a simple value, your recap is true.
But in reality the Provider often provides a big tree object containing many branches and leaves. However each Consumer may only need a small portion of it, even a single leaf value on that tree.
In this case there is a perf problem, cus the Context API is a whole sale solution. Even if you update a single leaf value, you still need to update the tree root object’s reference in order to signal a change. But that in turn notifies every single Consumer that useContext.
Now here’s the point that you’re missing:
It’s true that every one of them should be notified of a change, but it’s not true that all of them should re-render. Optimally only those Consumer which depend on the updated leaf value should re-render.
At its current state Context API doesn’t provide any fine grained control over this issue, thus things like use-context-selector bring back the selector pattern back into our sight.
Fundamentally this is a pub-sub model, and if you don’t have a mechanism to allow subs to decide which channel to tune in, the only thing you can do is to broadcast to all subs about everything. It’s like waking everybody in the neighborhood up just to tell them “Alice got a new mail”, which obviously is not optimal.
The very same problem exists in barebone Redux setup. Which is why selector pattern from react-redux used to be very popular.

How much it is mandatory to use state pattern i.e. mobx or redux in developing applications

I have seen it is becoming mandatory to use state pattern i.e mobx or rudux etc for building any react application. I am concerned that how much it is necessary to use these patterns. like if i have an application and have some pages in it each one is populating data with calling some restfull. So we can have handle it by adding some of optimization techniques like useEffect, usememo and usecalback and obviously custom hooks etc. Shouldn't it must be cleared before starting development application that how much we want to track changes in applications and how much global state we want to use. it we want only one global state ie auth status. we can use simple context and doing all with having simple techniques. So that our application become less dependent upon these libraries. If it is not like that which are the requirement that must be cleared before starting an application to decide whether to use state pattern or going with simpler version of react
It's not mandatory. In fact, my startup's codebase doesn't use any state management library. Just useState and for a few special cases we rely on useContext. React even offers useReducer (which is a simplified Redux).
I used Redux for a project and I hated it. If you're going to use it, follow the rules. Redux should be side effect free and immutable. When needing async data, you often have to use complicated middleware and other libraries (e.g. Redux-Sagas).
The good thing is that you can use a state management library for a part of your app. Maybe even another state management library for a different part.
In some cases, it might be useful to use a state management pattern. Certainly when it allows you to make some logic more understandable, readable or testable.
My take away: Always try to keep it simple

Is React Context API suitable for large scale applications

I am planning to build a large React application which might contain hundreds of components. But not sure what state management system to use between Redux and Context API.
Context API is in-built in React and doesn't need any third party library. It is easy to implement and solves the problem of sharing states at different levels of the component.
But on the other hand Redux is the Industry standard and has support for middleware to perform async actions.
If I choose Context API how can we manage API calls with it. Also do you think it is a good idea to use context for a large application where we might need state objects extensively.
The design benefit from Redux is that the action does not implement. An action is an indication that something happened (for example SAVE_PROFILE_CLICKED) but the action doesn't do anything (like connecting to api, sending data and saving response in state). You can do this with context api but the separation isn't enforced as much and you won't have the redux devtools. The pattern is called event store/sourcing. You could change the reducer and replay the events to see if your changes work and create a consistent state, testing is easier, extending is easier, logic is better isolated and probably many more benefits to be had.
The design also separates writing to state (reducer), side effects (thunk) and reading from it (selectors). This pattern (writing/reading separation) is called cqrs. Your query/selector is separated from the command/reducer. This gives you easier testing, isolation of logic, less chance of duplicate implementation and probably many more benefits.
You can still make a complete mess of your project when using Redux and not fully understand it so using Redux does not guarantee anything.
If I choose Context API how can we manage API calls with it.
You can do it any way you like it, the question is too general to answer.
Also do you think it is a good idea to use context for a large application where we might need state objects extensively.
As stated before; Redux is no guarantee your project won't be a mess. It will give you the tools to implement certain patterns with more ease. Make sure you understand it and it's patterns. Most example applications don't demonstrate why Redux is so powerful as the problem they implement (counter, todo app) isn't complex enough to even warrant using it. I can only advice you would write code that you're comfortable with and can understand.

Application design: how much Redux state?

I am currently working on building a small-ish app (6 front-end container-style components, 15 backend DB tables), in React.
I'm playing around with importing Redux, and I'm having a hard time designing my application state. It seems that I'll need to store broad-based state (e.g. user sessions) into Redux, but most of the other state I'll be pulling from the backend and won't necessarily need it to be always up-to-date 100%.
Based on all of the "redux-todo" style examples out there, it would seem that folks would store everything in their Redux store (since the state is relatively small, I suppose).
However, for my case, I'm wondering if Redux should just be a super lightweight store for only that which needs to be persisted across the app, a la an Ember-style 'service'.
Am I thinking about this correctly? Or should I be looking to store more state inside Redux?
I've struggled with that exact issue. It really takes some getting used to, but as another answer said, if you're using Redux, the idiomatic approach is to store basically everything in the Redux store. The one exception (and these turn out to be rather rare) is UI-only data such as a visibility toggle or dropdown info that sets a class or the like. That is, data that is ephemeral and has no impact on anything outside the component in which it lives. Even then, you will find that some UI-only items are easier to manage using actions and reducers as they often end up touching other items.
Edited to add: as the comment below states, the official Redux docs don't take a position on this issue. Which is one reason why so many people struggle with these issues -- you have a ton of freedom how to structure your React/Redux apps. My advice should be understood to be advice based on the experience of trying different approaches. Here are the three main reasons that I've seen for putting state into store:
more often than not, something I put into state ended up being needed somewhere else and getting it there through props required more work than if I had just put the data in the Redux store to start with.
Additionally, it's common to refactor your app to split apart components into more modular reusable pieces as your app grows and gets more complex. If you have data in state that is used in two places in a component you then won't be able split that component into smaller pieces easily.
Finally, readability. It's nice to be able to glance at your mapStateToProps call and see exactly what props are received or used by each component.
The problem is that Redux and putting everything in the store can feel like a lot of unneeded complexity when the actions and impact of the items is simple. If that's the case, I would suggest you consider dropping Redux at least at first. It will be a lot of work to refactor, but it will help you appreciate just where Redux shines. On the other hand, you may in fact not really need it for your app.
Back to your original question, yes, in my opinion, you should be putting more in the Redux store. Everything goes in there with a few very narrow exceptions. Data from the backend in particular should go into the store as a first step before getting mapped to props in the components where it's used.
The idea is to store any state your app needs to function. Your React components don't know anything about data other than what to do with it once they have it. The React/Redux "way" is that the only way they get it is from your Redux store.
So if you're fetching a list of items on app load, that goes in your store. Then your app has it for the entirety of the session. If someone leaves and comes back, or reloads the page, it fetches again, and stores a fresh copy in Redux again. You may not create any actions to modify it once it's there, and you may not need to fetch it again any time soon, but that's where it should live so your components can get stuff from it.
If you didn't store it in Redux where would you put it? And how would your components gain access to it?

Should large amounts of data go in the app state in redux?

Let's say I have some data tables, like a list an admin report returning a list of users (with details) or a list of log records. Does this info belong in the redux store, where only the data table needs this info? Or what if I am plotting something with 5000 nodes. Does this belong in redux single app state too? Why or why not?
If these items do not belong in the app state, is it just the local component state I should be loading these into without redux? Seems nice to have all my async requests fetched similarly, though.
Since redux doesn't care about the data (it literally just calls reducer functions when actions are dispatched, and replaces the current state with the result), there shouldn't be any performance issue there. It will be as performant as the reducer functions you provide.
If you'd like to see for this directly: https://github.com/rackt/redux/blob/master/src/createStore.js#L115
You're more likely to run into performance issues with react, but even then its doubtful at that scale. You may want to virtualize your table so that you're not rendering out of view elements, but that's a common problem with ui programming.
Yes, the Redux way is to put all your client-side state in the Redux store. See the Three Principles of Redux, the first one in particular:
Single source of truth
The state of your whole application is stored in an object tree within a single store.
This makes it easy to create universal apps, as the state from your server can be serialized and hydrated into the client with no extra coding effort. A single state tree also makes it easier to debug or introspect an application; it also enables you to persist your app’s state in development, for a faster development cycle. Some functionality which has been traditionally difficult to implement - Undo/Redo, for example - can suddenly become trivial to implement, if all of your state is stored in a single tree.

Resources