Redux/React-Redux - using "state = XXX" in the reducer - javascript

We're using Redux.js with React.js (React-Redux), and in the reducer we're using the following code (we are using redux-actions to reduce boilerplate):
const update = require('react/lib/update');
const myReducer = handleActions
({
[myAction]: (state, action) => {
state = update(state, {
something: {$set: !state.someProperty}
});
return someFunction(state);
}
})
We are using update from React Immutability Helpers but assigning the result into the state using state =.
Is this against the basic Redux guidlines - i.e. mutating the state? It seems so, but the code seems to work perfectly and quickly, and the redux devtools shows the state changes correctly...

Your example is okay, because you're not actually mutating the contents of the object that the state variable points to. Instead, you're just updating the local variable named state to point to a different object instead.
It's important to understand how variables and references work in Javascript, which will help clear up what's going on here.
Personally, I'd suggest doing const newState = update(....) for clarity.

Related

How to call a Function which is called inside Reducer, and the said Function uses the State returned by useReducer?

React v17
I am using useReducer. In the reducer function, I need to call another function check for network fetch. In check( ), I need to access the state, which is returned by useReducer.
I am getting this error can't access lexical declaration 'value' before initialization.
const reducerFilterdValue = (state, action) => {
let newState ;
switch(action.type){
case 'increment':
if(check()===0){
newState={...state,counter:state.counter+1}
}
break;
default:
newState={...state}
break;
}
return newState;
}
const check=()=>{
return state.counter%2
}
const [state, dispatchValue] = useReducer(
reducerFilterdValue,
initialFilterdValue
)
I have made a CodeSandBox for explanation purpose.
What is the efficient way to restructure the code?
A. Should I need to call check from outside the useReducer? To do that, I think I will need to use useEffect.
B. I declared check outside of the functional component as a let variable, and defined it after reducer function, but it was not working correctly and state was getting changed more than once.
C. Some better approach which I am unable to find out?
The reducer should be responsible for merging/reducing state and that is all.
As someone in your question comments said, there should not be any side effectw in reducer (only state manipulations). So change the state in the reducer and check the counter state by using useEffect and dependency variables.

Organizing and setting state from another file

I have some data (objects) that live in their own file (Origin.js).
This data is being exported using the spread operator within another object named OriginState:
Origin.js
//info
const info = {
title: '',
year: '',
};
//images
const images = {
logo: '',
heroImage: '',
};
//text
const text = {
header: '',
body: '',
};
export const OriginState = {
...info,
...images,
...text,
};
I am importing my OriginState object in another file and using it as state for my app like this:
OtherFile.js
import { OriginState } from './Origin.js';
const [state, setState] = useState({
...OriginState,
});
Here is an example handler where I am using this state to update some specified state values in an input later:
const handleChange = (e) => {
const { name, value } = e.target;
setState((state) => ({
...state,
[name]: value,
}));
};
Now, my question is... Is it incorrect to store state like this?
Additionally, am I using setState incorrectly in my handler function?
In most cases I've seen state declared and updated like this which is obviously easier to read:
const [count, setCount] = useState(0);
setCount(count + 1)
But I have a lot of state and didn't think it would be a good idea to have multiple setState hooks.
Is there a better way to do this? What I currently have just feels wrong.
Is it incorrect to store state like this?
const handleChange = (e) => {
const { name, value } = e.target;
setState((state) => ({
...state,
[name]: value,
}));
};
Nope, not at all, in fact, it is often the preferable pattern for state updates.
Any time your state update depends on the previous state, i.e. the classic counter example, or in your case, when there is nested state, you should use a functional state update to update from the previous state instead of the state from the previous render cycle.
Additionally, am I using setState incorrectly in my handler function?
In most cases I've seen state declared and updated like this which is
obviously easier to read:
const [count, setCount] = useState(0);
setCount(count + 1)
I see no issue with your state update logic in the handler. In this count example it would (should) be considered incorrect to update a count like this. See this codesandbox demo that attempts to show the issue between non-functional and functional state updates.
The correct state update should be setCount(count => count + 1)
But I have a lot of state and didn't think it would be a good idea to
have multiple setState hooks.
Is there a better way to do this? What I currently have just feels
wrong.
When it comes to form inputs and state I think it makes sense to have a single flat object. There isn't really a right or wrong answer in general though when it comes to using a single useState hook with "complex" state shape, or to use a single useState hook for each "chunk" of state. It's an opinionated answer, mostly do what makes sense for a specific use-case.
Generally though I'd say if a set of values are even loosely related then perhaps it makes sense to store them in a common object, but this is my opinion.
A potential issue I see with your imported data though is the chance that you may inadvertently overwrite some key-value pairs by the use of the Spread syntax.
export const OriginState = {
...info,
...images, // <-- could overwrite values from info
...text, // <-- could overwrite values from info and images
};
Your approach seems quite legit, and this is one of the best practices that if your newState is depend on the oldState use setState callback and get the old state from callback input because otherwise as you showed above if you use it like this:
const [count, setCount] = useState(0);
setCount(count + 1)
you may increase the chance to get stale data which will increase the potential for bug

What is the proper way to have a copy of redux state on a component prop

I have something like this:
ngOnInit() {
this.reduxServiceHandle = this.reduxService.subscribe(() =>
this.reduxUpdates()
);
}
reduxUpdates(): void {
const newState: TransportationControlTowerState = this.reduxService.getState();
// Check if feature groups object has changed
if (!isEqual(this.groups, newState.groups)) {
...
this.groups = newState.groups;
}
}
This is my reducer:
case TransportationControlTowerActions.ADD_GROUP:
return newState = {
...state,
groups: { ...state.groups, [payload.groupId]: payload.group }
};
break;
So, my question is: Do I need to clone deep the state before save it on this.groups prop? this.groups = newState.groups;
I think that every time I change the redux state, I return a new state object so there won't be a problem with my local state(this.groups) pointing to the old state.
But I just want to make sure I am not following any anti pattern.
Regards,
Official Redux docs says:
State is read-only
The only way to change the state is to emit an action, an object describing what happened.
This ensures that neither the views nor the network callbacks will ever write directly to the state. Instead, they express an intent to transform the state.
You can view full list of core principles here https://redux.js.org/introduction/three-principles

Mutating state on React manually, then calling setState

I'm studying React JS making a simple todo list and part of my code is as follows:
changeStatus(e, index) {
this.state.tasks[index].status = e.target.value;
this.setState((prevState) => ({
tasks: prevState.tasks
}));
}
As React documentation says, we shouldn't change state manually, we should call setState instead, so, I'm receiving a warning on console saying that, even calling setState after the manual changing.
I'm doing so because, for me, is more practical doing that than separating the task, doing a splice on my array, and calling setState with the new values.
Is there a problem with my approach? How would you recommend updating state in a efficient and non-repetitive way?
Try this :
changeStatus(e, index) {
const tmp = { ...this.state };
tmp.tasks[index].status = e.target.value;
this.setState(tmp);
}
You should not mutate the state directly, to do that create a new object from the state and then update that and set it back to state like
changeStatus(e, index) {
var tasks = {...this.state.tasks}
tasks[index].status = e.target.value;
this.setState({tasks});
}
In case you are wondering as to what {...this.state.tasks} do then check this answer:
What is the meaning of this syntax "{...x}" in Reactjs
However you can also make a copy of the state using the ES5 Object.assign() operator like
var tasks = Object.assign({}, this.state.tasks)

Does JSON.parse(JSON.stringify(state)) guarantee no mutation of redux state?

I've been tracking down an issue where a redux store correctly updates, but the component does not reflect the change. I've tried multiple ways of making sure I'm not mutating the state, however the issue persists. My reducers commonly take the form:
case 'ADD_OBJECT_TO_OBJECT_OF_OBJECTS': {
newState = copyState(state);
newState.objectOfObjects[action.id] = action.obj;
return newState;
}
For my copyState function, I usually use nested Object.assign() calls, but avoiding errors isn't so straightforward. For testing, to make sure I'm not mutating state, is it correct to use
const copyState = (state) => {
return JSON.parse(JSON.stringify(state));
};
as a guaranteed method of not mutating (redux) state, regardless of how expensive the process is?
If not, are there any other deep copy methods that I can rely on for ensuring I'm not mutating state?
EDIT:
Considering that other people might be having the same issue, I'll relay the solution to the problem I was having.
I was trying to dispatch an action, and then in the line after, I was trying to access data from the store that was updated in the previous line.
dispatchAction(data) // let's say this updates a part of the redux state called 'collection'
console.log(this.props.collection) // This will not be updated!
Refer to https://github.com/reactjs/redux/issues/1543
Yes this does a deep clone, regardless of how expensive it is. The difficulty (as hinted in comment under the question) is to make sure state stays untouched everywhere else.
Since you're asking for other approaches, and that would not fit in a comment, I suggest to take a look at ImmutableJS, which eliminates the issue of tracking state mutation bugs (but might come with its own limitations):
const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50
Your copyState() function will NOT mutate your state. It is fine. And for others reading this question, it is also fine to directly go
const newState = JSON.parse(JSON.stringify(oldState));
to get a deep copy. Feels unclean, but it is okay to do.
The reason you're not seeing your subscriptions run may be that Redux compares the new state (the output of the reducer function) to the existing state before it copies it. If there are no differences, it doesn't fire any subscriptions. Hence the reason that we never mutate state directly but return a copy -- if we did mutate state, changes would never be detected.
Another reason might be that we don't know what dispatchAction() in your code does exactly. If it is async (maybe because of middleware?) the changes wouldn't be applied until after your console.log() on the next line.

Categories

Resources