I'm building an Angular 1.5 app right now that I am incrementally adding Redux to. I'm coming across a problem that I'm having a hard time finding a good solution for. My application mostly deals with widgets that display data and manage the configurations that define what data is displayed in those widgets. As I add more state to Redux, the widgets reducer is getting enormous. I have broken it up into a few smaller reducers but now I'm wondering about the maintainability of a setup like this.
Essentially I worry that in the future it will be far too easy to modify one reducer or add a new action that operates on widget data and then forget to adjust all of the other reducers to account for this new aspect of the system. Or that the various ways that widget state can change via the reducers will get so complicated that it will be prohibitively difficult to make sure that I have properly adjusted all of the reducers necessary.
A lot of these reducers essentially just need to react when a widget is removed/moved/modified/whatever. I wish there was a simple catch-all way to do that in Redux like a deep watch in vanilla Angular(I know deep watchers should be avoided generally but just as an example). As I convert more of my app to use Redux, these reducers will just get more and more complex. It seems like it's a losing game. Is there some different way of approaching this problem that would be more appropriate for this problem space? Or should I just accept that some reducers are going to be enormous and that is how it has to be? Thanks for any input!
The solution to my specific problem is mostly to keep all state related to a given widget in one object so that I don't end up with redundant data management requirements. I split both state and reducers up and that caused the same logic for managing data cleanup and maintenance to be spread across different reducers. If I just split reducers up by implementing higher order reducers and keep the state in one place for one concept then this problem goes away.
Related
I would like to store a function / component inside the Redux state to pass it to another part of the application.
I've read that this is not recommended, but what exactly will happen?
Will it affect the performance of other data stored on Redux to the point it becomes visibly slow to the user?
Will it cause components that depend on this component or any data stored on Redux to continuously re-render causing the app to slow to a crawl?
Will Redux just break randomly all the time?
What are the actual, practical downsides to doing this, putting aside the intentions behind the creation of the library?
Do not put functions or components (non-serializable value) in redux state or actions
Avoid putting non-serializable this can break the following things:
debugging
server side rendering
state persistence
and more, this can create performance issue
This is described more in depth here in the official documentation
I've decided to just store the component I needed on Redux and haven't seen any downsides, which led me to believe there is no real issue in doing this, as long as you don't care about serialization.
According to Redux Style Guide, it is strongly recommended to connect more components to read data from the store.
For example, rather than just connecting a <UserList> component and reading the entire array of users, have <UserList> retrieve a list of all user IDs, render list items as <UserListItem userId={userId}>, and have <UserListItem> be connected and extract its own user entry from the store.
This, though, sounds a bit contradicting to what has been encouraged earlier in "Usage with React" section to separate presentational components from container components where the presentational components are to read data from props, not from the store.
Does this mean that:
It is best practice to keep the number of presentational components to minimum, hence increasing the number of stateful components?
Or the connected components can also be actually stateless components?
I'm a Redux maintainer, and I wrote the Style Guide page.
The short answer is that the Redux docs have been written over time, and thus some of the older docs page are out of date.
The Style Guide is our latest and current advice on how you should write your app.
We're in the process of rewriting the Redux core docs. That exact "Usage with React" page is something I intend to rewrite very soon, and when I do, I'll be dropping the terms "presentational" and "container" entirely.
I'd also encourage you to read my post Thoughts on React Hooks, Redux, and Separation of Concerns and watch my React Boston 2019 talk on Hooks, HOCs, and Tradeoffs to get some more thoughts on how hooks change the way we think about writing components.
Like everything in programming, there is a balance.
On the one hand, you have separation of concerns, making sure each block of code is focusing on one task. This can help reduce the complexity of a given component.
On the other hand, you have reduction of parameters, reducing the brittleness of your code by keeping track of fewer parameters at any given moment.
The first bullet is typically required when your state management is complex, or you have to manage server connections, and want to keep that work separate from the presentation to reduce confusion.
Redux takes care of that for you, by putting that code into the reducer. If you use the connect() higher-order component, that's exactly what you're doing: creating a component to translate state for your base presentation component. The useSelector() and useDispatch() hooks are another way of reducing the state management code in your component.
Redux stresses the second bullet because Redux's purpose is to reduce the clutter to the point that you don't need to separate your code into presentation and business logic components. Instead of passing several props back and forth, you can pass a single key, make a simple function inside your component to retrieve the data, and get on with the presentation directly.
The folks who wrote Redux also want to reassure folks that Redux is quite fast, and not to be afraid to use it generously.
My own experience is that Redux manages the business logic side of things well enough that I rarely need to create a separate wrapper component for business logic. The state code is a few lines calling hooks at the top, and that's it.
If I do have complex business logic, typically it involves deciding what state to display. That involves determining which key to use in my Redux state. So I might put all that logic into a wrapper, but the end result of the wrapper is a single key that my presentation component uses to pull the appropriate state from Redux.
I could not understand what the below lines on first page of REDUX mean https://redux.js.org/introduction/motivation
This complexity is difficult to handle as we're mixing two concepts
that are very hard for the human mind to reason about: mutation and
asynchronicity. I call them Mentos and Coke. Both can be great in
separation, but together they create a mess. Libraries like React
attempt to solve this problem in the view layer by removing both
asynchrony and direct DOM manipulation. However, managing the state of
your data is left up to you. This is where Redux enters.
Note: Marked bold are the strong lines I was enable to understand.
Mutation simply means you will need to be able to change the state of things (variables, global store etc) and also you will need to be able to react to when those things change.
Asynchronicity means that events may occur at different times - you can't predict precisely when they will occur or when they will complete.
Therefore, in an app that has to be able to change data (mutation) and can have that data change asynchronously, things get difficult.
I'd suggest you read up more on redux (and in general, libraries that promote a specific "flow" of data mutations). At the heart of the issue is that if data can mutate at any time whereby you are changing the data directly and it can be changed asychronously (for instance via API calls to external services) then without careful thought/use of libraries and understanding, your app can turn into an unholy mess.
I'm learning redux and am struggling to understand why state has to be immutable. Could you provide me with an example, in code preferably, where breaking the immutable contract results in an not so obvious side effect.
Redux was originally invented to demonstrate the idea of "time-travel debugging" - being able to step back and forth through the history of dispatched actions, and see what the UI looks like at each step. Another aspect is being able to live-edit the code, reload it, and see what the output looks like given the new reducer logic.
In order to be able to properly step back and forth between states, we need to make sure that reducer functions have no side effects. That means data updates need to be applied immutably. If a reducer function actually directly modifies its data, then stepping back and forth between states will cause the application to behave in an unexpected fashion, and the debugging effort will be wasted.
Also, the React-Redux library relies on shallow equality checks to see if the incoming data for a component has changed. If the data references are the same, then the wrapper components generated by connect assume that the data has not changed, and that the component does not need to re-render. Immutable data updates means that new object references are created, and thus connect will see that the data has changed and the UI needs to update.
Two Ideas
There are two ideas about immutability that you need to understand:
Mutate the state only in the reducers
Using an immutable data structure
Mutate the state only in the reducers
Redux tries to ensure that you only mutate the state in the Reducers. This is important because it makes easier to think about your application data flow.
Let's say that a value is not displayed in the UI as you expected or that a value that should have changed still showing its original value.
In that case, you could just think about which reducer is causing the mutation and see what went wrong.
This makes thinking about Redux issues very simple and makes developers highly productive.
Sometimes you can mutate the state in a view or in an action by mistake. If you think about the life-cycle:
Action -> Reducer -> Views-> Action
If the state changes in a view and then an action is triggered the state change could be override. This would make very hard to find out what is going on for developers. We solve this by mutating state only in the reducers.
Note: another nice thing is that reducers are pure functions and all the async stuff takes places in the actions/middleware. So all side effects take place in the actions/middleware layer. Actions are our connection with the external world (HTTP, Local Storage, etc.).
Using an immutable data structure
As I have already mentioned, sometimes you can mutate the state in a view or in an action by mistake. To reduce chances of this happening we can make state mutations explicit by using and immutable data structure.
Using immutable data structures also has performance benefits because we don't need to perform deep equality checks to check for mutations.
The most commonly used provider of immutable data structures is immutable.js.
I think the key ideas would be
Easy to replay any given situation/test flow - you can replay with the same actions starting from the same initial state
Fast component re-rendering - because the reference is changed and not its values, it is much much faster to test the change
Of course, there are other aspects and I suggest this nice article which explains in more details.
We're planning an Electron app, and we're considering Redux. The app is going to have a huge state, data potentially being read from dozens or hundreds of files. While doing some research to understand Redux, I found that the reducer method must never alter the state, and must create a new state with any changes. This is going to be a problem if the state of the app is very large, since now we need to deeply copy everything in the state (it's going to be a bunch of nested objects), and we're going to take up double the memory that was being used to hold the state once, then the old state will be deleted. This doesn't seem ideal at all.
Is there a better way to handle this in situation in Redux?
You shouldn't deeply copy. In fact, I'd say deep-copying is counter-intuitive to the redux-way.
You'll be copying by reference most of the time, which is very fast.
If you're worried about your state tree being too large, I recommend redux-ignore, to break things down.
I'm currently running a redux app that has over 200 reducers. I've yet to detect a bottleneck due to redux, even on cheap android mobile devices.