It looks like modifying a react state directly is a bad practice (Do Not Modify State Directly) and the reasons have already been discussed many times on SO (see a question) and outside in the web (see a blog post).
The problem seems to be that when the state is modified directly, changes are made to the state in the current virtual DOM. So when it's time for a rerender to occur, the state in the new virtual DOM would be the same as the state in the previous one and the actual DOM would not be updated.
But what if you don't want to trigger a rerender when modifying the state? In this case It should be safe to modify the state directly, isn't it?
Below is described the situation I am facing, which brought me into the rabbit hole of assessing whether I can safely modify the state directly, under certain circumstances.
I have a react useReducer state consisting of an object with many keys.
const initialState = {
a: 0,
b: 1,
c: 2,
d: 3,
};
In the state reducer I don't always return a new state. Say that if I want to modify a or b then I also want to trigger a rerender, but if I modify c or d I don't want because the DOM is not being affected anyway.
I think of c and d as sort of refs: they are mutable within a single render, yet their value is preserved between rerenders.
I could, of course, limit the state to only a and b and create a ref for each of c and d, but they are all related and I often need to pass them all together to a function. So in my case it works better to keep the refs in the state.
Here's how the reducer would look like:
// When modifying `a` or `b` `action.modifyRef` is set to `false`.
// When modifying `c` or `d` `action.modifyRef` is set to `true`.
const reducer = (state, action) => {
if (action.modifyRef) {
state[action.key] = action.value;
return state;
}
const newState = {
...state,
[action.key]: action.value,
};
return newState;
};
Am I right to believe I can safely modify the state without triggering a rerender, if a rerender is not desired in the first place?
If the answer to the previus question is "yes", then to modify c or d I can just modify the state directly instead of dispatching an action against the reducer, can't I?
state.c = 5;
// the above statement is equivalent to the one below
dispatchState({ modifyRef: true, key: 'c', value: 5 });
I don't think the c and d you're describing (members that shouldn't cause re-rendering, and which are not used for rendering) are state information as the term is used in React. They're instance information. The normal way to use non-state instance information in function components is to use a ref.
On a pragmatic bits-and-bytes level, can you hold non-state information in state and modify it directly instead of using a state setter (directly or indirectly)? Yes, you probably can, at least initially. The only scenarios where I can see that causing incorrect behavior of the app/page involve rendering, and you've said they aren't used for that.
But if you do:
It'll be confusing for other team members (or you yourself, if you have to come back to the code after a break). Semantics matter. If you call it state, but it's not state, that's going to trip someone up. It's like calling something a function that isn't a function: at some point, someone's going to try to call that "function."
It'll be a maintenance hazard, because a team member (or you yourself after a break) may make an innocuous change such that c or d are used for rendering (because after all, they're in state, so it's fine to use them for rendering), perhaps by passing one of them as a prop to a child component. Then you're in the situation where the app won't rerender properly when they change.
A slight tangent, but in a comment on the question you mentioned that you "...they are all related and I often need to pass them all together to a function..."
Using a ref to hold c and d, the set up might look like this:
const [{a, b}, dispatch] = useReducer(/*...*/);
const instanceRef = useRef(null);
const {c, d} = instanceRef.current = instanceRef.current ?? {c: /*...*/, d: /*...*/};
Then getting an object in order to treat them as a unit is:
const stuff = {a, b, c, d};
// ...use `stuff` where needed...
Creating objects is very in expensive in modern JavaScript engines, since it's a common operation they aggressively optimize. :-)
I basically understand the difference between object and function setState in React in terms of atomicity and resiliency to batch updates.
// Subject to batching issues.
this.setState({
x: this.state.x + 5
});
Or should I use:
// Atomic since previous state is known
this.setState((state, props) => {
x: state.x + 5
});
However, I wonder what could be the advantages of using functional setState if previous state is irrelevant.
this.setState(() => {
food: "Bars"
});
I don't think there are any advantages, as they are basically the exact same function call, just using an object vs a function. So if your update doesn't depend on the current state or current props, then use an object. If it does require previous knowledge, use the function type.
Both versions also allow you to use a callback to access the state after the state has changed, so there is no difference in abilities either.
The only reason I might use the function version everywhere is just for consistency, or in situations where I am currently not using the previous state, but might in the future.
setState with object as parameter does shallow comparison with previous state.
If your value is dependent on previous state it is good practice to use setState with function(as setState is async operation) otherwise just pass Object as parameter.
There should not be performance issue in either case.
I am new to redux and studying this framework. As suggested in the document, it requires to return new state objects, instead of mutating the previous state. I just don't understand the purpose of this. JS arguments are call by value so changing arguments will not update the original value. Then what is the point of doing this?
Great question. First of all, your understanding of the pass by value of JavaScript is not accurate. You could refer to this question about what exactly the underlying theory of this issue.
So the situation is that the item passed in is passed by value. But the item that is passed by value is itself a reference. Technically, this is called call-by-sharing.
That explained, another question is why should we maintain the previous states?
The answer is quite simple: Because we want the states to be trackable and more managable. And Redux is exactly the tool to help us manage that.
Imagine there is a stack of a few states, if you mutate the state itself, rather than making a copy and push it onto the top of the stack. You would find it hard to go back to the previous state. And it becomes less managable and vulnerable to large applications.
In Redux, every time we dispatch an action, we process it in an immutable way. That way, all the states are in perfectly control, and thus we could traverse through history. That's the zen of Redux IMO.
Hope that helps!
JS arguments are call by value so changing arguments will not update the original value.
Indeed, but if the value passed is an object reference (as it is with objects), and you use that object reference to change the state of the object, then it's not a pure function anymore.
For instance:
function notPure(obj) {
// Modify object state
++obj.counter;
}
var o = {counter: 0};
notPure(o);
console.log(o.counter);
Instead, we'd be expected to return a new object with the modification:
function pure(obj) {
return {counter: obj.counter + 1};
}
var o = {counter: 0};
var o2 = pure(o);
console.log(o.counter);
console.log(o2.counter);
Whilst learning Redux I've came across Reducers. The documentation states:
The reducer is a pure function that takes the previous state and an action, and returns the next state. (previousState, action) => newState. It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue) .
MDN describes the reduce method as:
The reduce() method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.
I'm still confused on why the Redux definition of a reducer as it's making no sense. Secondly the MDN description doesn't seem correct either. The reduce method isn't always used to reduce to a single value. It can be used in place of map and filter and is actually faster when used in place of chaining.
Is the MDN description incorrect?
Jumping back to the Redux definition of a reducer, it states:
It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue)
I'm under the impression that a reducer in Redux is responsible for modifying state. An example reducer:
const count = function(state, action) {
if(action.type == 'INCREMENT') {
return state + 1;
} else if(action.type == 'DECREMENT') {
return state - 1;
} else {
return state;
}
}
... I don't see how this is a function that would be passed to reduce. How is that data being reduced to a single value? If this is a function you would pass to reduce then state would be the callback and action would be the initial value.
Thanks for any clear explanations. It's difficult to conceptualize.
The term "reduce" is actually a functional term used in functional programming. In a language like Haskell, F# or even JavaScript, we define a transformation that takes a collection (of any size) as input and returns a single value as output.
So (not to be pedantic, but I find this helps me) think of it visually. We have a collection:
[][][][][][][][][][]
...which we want to collapse into a single value:
N
Programming functionally, we would do this with a single function that we could call recursively on each element of the collection. But if you do that, you need to keep track of the intermediate value somewhere, right? Non-pure implementations might keep some kind of "accumulator" or variable outside of the function to keep track of the state, like so:
var accumulator = 0;
var myArray = [1,2,3,4,5];
myArray.reduce(function (each) {
accumulator += 0;
});
return accumulator;
With pure functions, though, we can't do this - because by definition, pure functions can't have effects outside of their function scope. Instead of relying on an external variable that encapsulates our "state" between calls, we simply pass the state along in the method:
var myArray = [1,2,3,4,5];
return myArray.reduce(function (accumulator, each) {
return accumulator + each;
}, 0);
In this case we call the function a "reducer" because of its method signature. We have each (or current - any name is fine), representing an object in the collection; and state (or previous), which is passed to each iteration of the function, representing the results of the transformation we've already done to the previous elements in the collection.
Note that the MDN documentation you referenced is correct; the reduce() function always does return a single value. In fact, the reduce method in any language is a higher-order function that takes a "reducer" (a function with the method signature defined above) and returns a single value. Now, yes, you can do other stuff with it, if your function that you call has side effects, but you shouldn't. (Essentially, don't use .reduce() as a foreach.) Even if the method you call with reduce has side effects, the return value of reduce itself will be a single value, not a collection.
The cool thing is, this pattern doesn't just have to apply to arrays or concrete collections, as you've seen in React; this pattern can be applied to streams as well, since they're pure functions.
Hope this helps. For what it's worth, the definition on the Redux site could be improved (as the concept of a reducer isn't just because of Javascript's Array prototype method). You should submit a PR!
Edit: There's a Wikipedia article on the subject. Note that reduce has different names, and in functional languages, it's commonly known as Fold. https://en.wikipedia.org/wiki/Fold_(higher-order_function)#Folds_as_structural_transformations
Edit (2020-10-03): People still seem to be finding this useful - that's good. With time, I've realized that "fold" is a much better term for this; the functional languages got it right. "Reducer" isn't really a bad term, but it's not necessarily a good one, either.
The reason why a redux reducer is called a reducer is because you could "reduce" a collection of actions and an initial state (of the store) on which to perform these actions to get the resulting final state.
How? To answer that, let me define a reducer again:
The reduce() method applies a function (reducer) against an accumulator and each
value of the array (from left-to-right) to reduce it to a single
value.
And what does a redux reducer do?
The reducer is a pure function that takes the current state and an
action, and returns the next state. Note that the state is accumulated as each action on the collection is applied to change this state.
So given a collection of actions, the reducer is applied on each value of the collection (from left-to-right). The first time, it returns the initial value. Now the reducer is applied again on this initial state and the first action to return the next state. And the next collection item (action) is applied each time on the current state to get the next state until it reaches the end of the array. And then, you get the final state. How cool is that!
Sorry, but I would disagree with previous answers. I would not support the naming reducer. I'm passionate about FP and immutability. Don't blame me, read the second part, but I want to state first, why I disagree.
It's correct that the reducers are the sequence of transformations, but the sequence itself - could be part of another sequence. Imagine it, like links - a part of chain. But the chain itself could be part of longer chain. Each link is the "transition" of the global state. Than, what the theory behind it?
Isn't it actually the "Finite state machine"? - close, but not. It's actually the Transition system.
A labelled transition system is a tuple (S, Λ, →) where S is a set of states, Λ is a set of labels and → is a set of labelled transitions
So, S - are set of our states
Λ - is our so-called "actions" (but labels in theory)
... and
→ - reducers "labelled transitions"! I would name it so, if I am creator of this library.
Understanding this theory helped me to implement my library, where I can have low-level transition system as a part of high-level transition system (like chain - still could be part of longer chain) - and still having single global Redux state.
Calling Redux reducers reducers is semantically incorrect and doesn't make much sense.
That's why the author is confused.
A reducer is a function that reduces a set of values to a single value.
We can also say that it folds the values - thus the classic fold() fn in functional programming.
Since Redux reducer does not fold a set of value, but applies an action to a state and always returns the same shape (State -> Action -> State) - it should be called applicator or applier.
But, since we have to always return the same shape of the state, and not just smth completely unrelated - we'd make much more sense calling Redux state applicators - changers, transformers or mutators.
And, it has indeed become commonplace to use terms like 'mutate the state' and 'state mutator'.
But Redux sounds just so much cooler, than Applux or Mutux :)
I'm under the impression that a reducer in Redux is responsible for modifying state. An example reducer:
const count = function(state, action) {
if (action.type == 'INCREMENT') {
return state + 1;
} else if (action.type == 'DECREMENT') {
return state - 1;
} else {
return state;
}
}
... I don't see how this is a function that would be passed to reduce. How is that data being reduced to a single value? If this is a function you would pass to reduce then state would be the callback and action would be the initial value.
// count function from your question
const count = function (state, action) {
if (action.type == 'INCREMENT') {
return state + 1;
} else if (action.type == 'DECREMENT') {
return state - 1;
} else {
return state;
}
}
// an array of actions
const actions =
[ { type: 'INCREMENT' }
, { type: 'INCREMENT' }
, { type: 'INCREMENT' }
, { type: 'INCREMENT' }
, { type: 'DECREMENT' }
]
// initial state
const init = 0
console.log(actions.reduce(count, init))
// 3 (final state)
// (INCREMENT 4 times, DECREMENT 1 time)
These answers are good but I think it's much simpler than you think. I'll admit that I had a similar confusion at first. The reduce method on Javascript arrays takes an accumulator and a callback function.
const arr = [1, 2, 3]
const sum = arr.reduce((accumulator, element) => {
accumulator += element;
return accumulator;
}); // sum equals 6 now
The reason it's called a reducer in redux is because it roughly has a similar structure.
const sum = arr.reduce((accumulator, element) => { // accumulator is the initial state
accumulator += element; // we do something to modify the initial state
return accumulator; // we return that and it becomes the new state
});
So every time we run a reducer we take something in, modify it, and return a copy of that same thing. On each iteration we're pointing to the same thing. Ok, yes, we have to make a copy in redux so as to not directly modify state, but symbolically every time we run it, it's kind of like the way reduce starts with an initial state in the example above of 1. Then we add 2 to our inital state and return 3. Now we run our 'reducer' again with an intial state of 3 and add 3 and we end up with 6.
It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue)
Array.reduce
This is very similiar to what you would pass to Array.reduce as the callback (reducer). The important part being:
callback
Function to execute on each value in the array, taking four arguments:
previousValue
The value previously returned in the last invocation of the callback, or initialValue, if supplied. (See below.)
currentValue
The current element being processed in the array.
Where state is the "previousValue" and action is the "currentValue".
Both Array.reduce and Redux take a "reducer" function, which reduces previous and current into a single return value.
const reducer = (previous, current) => {
/* calculate value */
return value;
};
previous
current
return
Array.reduce
accumulator
current element
new accumulator
Redux
state
action
new state
Redux docs:
If we were to create an array of Redux actions, call reduce(), and
pass in a reducer function, we'd get a final result the same way:
const actions = [
{ type: 'counter/incremented' },
{ type: 'counter/incremented' },
{ type: 'counter/incremented' }
]
const initialState = { value: 0 }
const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult)
// {value: 3}
We can say that Redux reducers reduce a set of actions (over time)
into a single state. The difference is that with Array.reduce() it
happens all at once, and with Redux, it happens over the lifetime of
your running app.
It's a confusing name because in practice it has nothing to do with Array.prototype.reduce.
In the Redux source code here, this is how your reducer is called:
currentState = currentReducer(currentState, action)
It's enticing to use the term because your state at any point in time could be found by calling arrayOfPreviousActions.reduce(reducer).
['INCREMENT', 'INCREMENT'].reduce((currentState, action) => currentState++, 0)
But it's not actually called like this.
The real question is what would be a better name for this?
If we don't consider the output/purpose, it's handling an action/event. An actionHandler, actionProcessor.
Aside: event is probably a better term than action too because Redux is often also used to respond to API responses which results in action types set to GET_USERS_SUCCESS...which is clearly an event. This was simply adopted from Flux though.
Redux draws inspiration from the Event Sourcing pattern and State Machines/Transition Systems. Googling around revealed the term state transition often used.
state transitions can be represented as functions that take a state and an event, and produce a new state
But what do we call these "functions"?
IMHO, reducer is a poor name because it doesn't encode any meaning about its purpose, just what it looks like, but I can't think of a better one.
In the end, all names for things were invented at some point, and given its popularity, reducer is now associated with state reducer or state transition function.
Just look at the origin of the term argument for example:
The use of the term "argument" developed from astronomy, which historically used tables to determine the spatial positions of planets from their positions in the sky. These tables were organized according to measured angles called arguments, literally "that which elucidates something else."
It's not a very specific name, but the reducer in Redux does the same thing that other reducer functions do. They take in multiple things and give us back one thing. The Redux reducer takes in two things (the previous state and an action to be performed on that previous state) and reduce those two things down to one: the next state.
Consider the following:
[SELECT]: (state, action) => {
let newState = {...state, newState}
delete newState[action.payload.key]
return(newState)
}
Why is it acceptable for me to mutate the shallow copy, return it and still satisfy the rule about not mutating my state?
It is acceptable because (at least in your example code), the mutation is on the shallow object, so you are not modifying an object that any other code is currently referring to.
It would not be acceptable to make a shallow copy and then modify a nested object! The key is that you clone any and all objects in the object tree that are on the path to the deep property you wish to change.
From the Redux FAQ:
It’s important to remember that whenever you update a nested value,
you must also return new copies of anything above it in your state
tree. If you have state.a.b.c.d, and you want to make an update to d,
you would also need to return new copies of c, b, a, and state. This
state tree mutation diagram demonstrates how a change deep in a tree
requires changes all the way up.
A reducer in Redux should be pure, which means it must guarantee that it doesn't change or effect anything outside of it's own function (i.e. produces no side effects). If you mutate the passed state object directly, you violate this.
By creating a new copy, you know for certain that no other part of your application uses it (it was just created, so how could they?). Which in turn means that it's safe to mutate it without worrying about strange side effects.