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.
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.
As redux-saga describes itself as
a library that aims to make application side effects (i.e.
asynchronous things like data fetching and impure things like
accessing the browser cache) easier to manage.
The select effect is just used to get a slice of the current Store's state. It doesn't produce any side effect at all (no I/O operation, no mutation,etc). It's just a purely functional operation. Why a purely functional operation was designed to be an effect ?
Because none of your saga code is supposed to be interacting with the store directly overall. Whatever your saga needs to do, whether it be making an AJAX call, dispatching an action, or anything else, gets done by yielding an effect description and asking the middleware to do that work for you. Your saga doesn't have access to the store directly to call dispatch(), so there's no reason for it to have access to getState() directly either.
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.
Maybe someone can explain this to me, but these two principles, in my opinion, contradict each other and it's really hampering my progress in learning Redux.
State is read-only and Changes are made with pure functions
The first line after State is read-only is this: "The only way to mutate the state...". If something is read-only then it shouldn't be able to be mutated in the first place. Even in the Egghead videos the states are always labeled as const, but are still changed defeating the purpose of const.
Can someone please explain to me how these are not diametrically opposed ideas?
There's no contradiction, though certainly the confusion is understandable.
A good way to think about it is to draw an analogy between React state and Redux state.
In React, you only ever define your component state once, in the constructor() or getInitialState() method. From that point on, you always treat this.state as read-only, you never assign anything to this.state. To change the state, you call this.setState().
The same is true in Redux. You define the state - or each different part of the state - once. Typically it's called initialState and assigned as the default value of the state parameter of your reducer. Treating state as read-only means never overwriting or mutating the state. The only way to change the state is with the return value of your reducer.
Or, in short...
I think your real hang up is over the state being both changeable and read-only, so let me be more pithy:
A CD-ROM is read only, but I can eject the CD and replace it with another.
The state is read-only, as far as the world outside Redux is concerned. Think of it as a view on the append-only sequence of application events (actions) that have happened. You can generate new actions, and those will result in the state becoming different.
Of course, it's the real world and there is mutability somewhere. As Rich Hickey famously put it:
“If a tree falls in the woods, does it make a sound? If a pure
function mutates data to produce an immutable value, is that ok?” ~
#richhickey
But as much as is practically possible, you write your application without directly mutating things. Instead, you describe the changes you would like to see (dispatching actions), and a framework (Redux) carries them out, using your reducers to create the entire view of the store (the state) at once, and through React's props, a new view is generated as a result.
This is inspired by the concept of functional reactive programming. In this paradigm, all functions are pure, and you handle the concept of change over time by representing "what you would do with a value, if it were to have changed". In some sense, you conceptualize the entire past and future of a variable as an immutable function, whose future you simply happen not to know yet. Sorry if that got too philosophical.