Why does slice push work in redux but not concat? - reactjs

Recently I change to slice and I seeing some odd behavior can someone explain:
const initialState = {
[]
};
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {
loadUsers(state, action) {
state = state.concat(action.payload);
},
},
});
when i do something like this the state wont change and payload dont add to state array
i can go like
for(let i=0; i < action.payload.length; i++) {
state.push(action.payload[i]
}
and everything work fine, then I realized if name the state initial state like:
const initialState = {
users: [],
};
Then I can go like:
state.users = state.users.concat(action.payload);
on loadUsers reducer and this work fine too
can someone explain why concat wont work on first attempt when i have initial state of [] without naming it

The problem here is not push or concat, but the fact that
state = something
will never do anything.
Redux Toolkit watches the object in the variable state for modifications - but that assignment throws away that object and puts a new object into the variable instead of changing it.
That cannot be observed.
Instead, you can do
return something
For more information, see Writing Reducers with immer, especially resetting and replacing state, which says:
A common mistake is to try assigning state = someValue directly. This will not work! This only points the local state variable to a different reference. That is neither mutating the existing state object/array in memory, nor returning an entirely new value, so Immer does not make any actual changes.

Adding on to the previous answer, to make one distinction. You should not attempt to mutate the entire state as in the line
State = something
But you can and should mutate state properties in this way, in fact it is one of the major benefits of redux toolkit and immer, so
State.prop = something
Is ok.
This is a good time to reiterate this kind of state mutation is only ok in redux toolkit where immer is doing it’s work.
Again here is the link to the relevant docs
https://redux-toolkit.js.org/usage/immer-reducers#resetting-and-replacing-state

Related

Using setState from inside a reducer. Reducer updates state twice instead of once

I am new to React and I'm trying to build a calculator in React.js. I have a display that contains two fields: one to output the current value and the other one to store a "slice" of the expression. I'm basically trying to immitate the Windows calculator. I use useReducer hook to update state depending on the type of the button pressed. In the dispatch object, I send references to two useState hooks declared inside the App. These hooks are: (1) a useState boolean to indicate whether I am waiting for a second amount, and (2) a useState to save a slice of an expression so it appears in the paragraph above.
The problem is in the reducer function, in the "multiplication_operator" case. When I click on the multiplier sign for the second time, I expect the previous state array ['first amount', 'operator'] to concatenate with the 'second amount'. Basically, I expect an array where index 0 is the first amount, index 1 is the operator and index 2 is the second amount. However, when I try to add the second amount at index 2, it adds the same second amount at the next index (3), which is unexpected. Link to Codesandbox
Link to gif that reproduces the bug
I assume the problem might come from the fact that I use and update too many states from inside the reducer function.
My question is what is the source of the problem and how do I fix it?
Is it the issue of poor choice of hooks or something else? Reducer triggers a chain of updates from App and then back to reducer? I'm really out of ideas here.
It's actually the correct behaviour for useReducer to be called twice. (Details here: https://github.com/facebook/react/issues/16295)
For your app to work as intended despite this, you would have to store/update value, firstNumber and operatorIsActive within the reducer instead of calling setFirstNumber and setOperatorIsActive to update them.
e.g. you could set the initial state of your reducer as
const initialState = {
value: "0",
firstNumber: [],
operatorIsActive: false
};
instead of just storing the initial value.
const initialValue = "0";
In App.js you can access the reducer values this way
const [state, dispatch] = useReducer(displayValueReducer, initialState);
const {value, firstNumber, operatorIsActive} = state;
When the multiply button is clicked, you could access/update the values this way:
case "multiplication_operator": {
const operator = ` ${action.value} `;
if (state.operatorIsActive === false) {
return {
...state,
firstNumber: [state.value, operator],
operatorIsActive: true
};
} else {
return {
...state,
firstNumber: [...state.firstNumber, state.value]
};
}
}
Changes like the above would ensure that your reducer is pure (elaboration here: https://www.geeksforgeeks.org/pure-functions-in-javascript/)

Cloning an object within a redux reducer

I've recently learned React and Redux, and right when I thought I was getting the hang of it I've been hung up on this.
In my reducer I'm under the impression that the spread operator should create a new instance of the object at a difference memory location. However, using === to compare the objects returns true which I believe means the objects are the same instances.
const initState = {
loadcases: {
1: {
isActive: false,
profile: 'UB'
},
2: {
isActive: true,
profile: 'PFC'
}
}
}
const designInput = (state=initState, action={}) => {
let newState = {...state}; //clone design input state
console.log(newState.loadcases === state.loadcases)
// Prints: TRUE ---- WHYYYYYYY????????????
// goes on to modify newState before returning it.
}
The redux logger is showing my previous state to already equal the new state which has me thinking that this here is interfering with the store directly. I've read through some redux docs and other posts which make me believe I have set everything else up correctly.
Any resources to educate me more on this issue is also much appreciated.
UPDATE:
Jayce's answer was exactly what was happening.
This SO post outlines which method for a deep clone will be suitable. For me, it's likely I'll want to store dates and other data so I've chosen to use lodash cloneDeep method.
So now: console.log(cloneDeep(state) === state.loadcases); // false
Your assumption that the spread operator creates a new object is true, however it doesn't do a deep clone of objects. It copies the references of nested values over to the new object, so that's why it's printing that they're equal.
If you want to deeply clone something with nested values you can't use spread, you have to use another method. You could use a library like lodash, or something such as JSON.parse(JSON.stringify(obj)), there's multiple options just google for something like "Javascript deep clone object"

Giving React state a name in DevTools

When using React useState(), if you have something like the following const [cartItems, setCartItems] = useState([]) it shows in React DevTools as State: []. Other than changing the state from an array into an object containing an array such as useState({ cartItems: [] }) is there anything else I can do to show a key for the state? If not, is this bad practice to use an object or am I ok using this? Reason I want to have a key, is when debugging if you have a number of different items of state, the DevTools just shows as State: [], State: {[...]}, State: [] etc which is difficult to see what is what.
Not really since the useState hook uses array destructuring to assign variable names, i.e. the names are only used within the function, not in the react framework.
const [someSpecialAwesomestate, setSomeSpecialAwesomestate] = useState();
equates to
const stateObject = useState();
// stateObject[0] is the state
// stateObject[1] is the callback state mutator function
From what I understand, React further obfuscates this by simply storing the hooks themselves internally in an array.
It may be a bit more verbose to store objects with a single root key, and tedious to manage state updates, but if it helps your dev flow to see what each state object IS in the devtool, then why not do what helps you code better?

React/Redux: componentDidUpdate not firing consistently

I have nested arrays in my props, and the user can re-order the second-level array by dragging and dropping column. I (using redux) make a shallow copy of getState's outer array, do the re-ordering (through re-assignment of array objects based on index), and return the new array to the state (using spread operator in my reducer). However, componentDidUpdate is not called if this ordering is the first thing I do after page load. If I take other actions that update the props/state, and then attempt to re-order, the ordering works fine and componentDidUpdate is called.
In the former situation, where ordering is the first thing I try to do after page load/refresh, the render function is hit (with the properly re-ordered props) but the DOM never updates, presumably because redux does not recognize a state change?
It just seems like some other props change (even if unrelated to arrays) "wakes up" redux or connect and allows it to recognize changes.
Here's where I copy, dispatch and then release the updated state in the reducer:
in actionCreator method:
...
const newProjects = getState().tracker.projects.slice().map(p => {
if (p.innerArray) {
const from = p.innerArray[draggedId-1];
const to = p.innerArray[droppedId-1];
p.innerArray[draggedId - 1] = { ...to, order: draggedId };
p.innerArray[droppedId - 1] = { ...from, order: droppedId };
}
return p;
})
dispatch({ type: 'RECEIVE_FIELD_UPDATE', projects: newProjects, message: "Sorted" });
reducer:
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: action.projects,
message: action.message
}
As you can see, I'm not mutating state in the reducer or doing other anti-pattern things (as far as I'm aware).
FWIW, the "Sorted" value on message property seems to get through whether or not componentDidUpdate is hit (I have a modal that pops up with the message to confirm).
Any pointers or related documentation would be helpful; I've been reading all I can find, including other StackOverflow posts, but to no avail.
UPDATE
Solved the issue, it did have to do with redux not diff-ing the properties but not because of what I expected, read below if interested.
Figured this out, and per usual I was looking in the wrong place.
The issue was that my conditional check on page load to prevent re-loading of already stored data was preventing any dispatches from firing. When I looked in the reducer, the sub-arrays of the projects were identical after dragging, because the state had not been previously connected yet. I thought a hard refresh would cause a full re-load but apparently I need to read more about how redux stores data and how to refresh that.
Anyways, fixing this condition so that I get new data on page load fixed the issue and componentDidUpdate is now being triggered consistently.
When updating state in a reducer in an array format, I've had to spread state again in order for updates to happen and keep from mutating.
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: ...state, ...action.projects,
message: action.message
}
or...
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: [...state, ...action.projects],
message: action.message
}
Depending on the format.
Hope that helps.

How to access nested Redux state in React components?

What's an efficient/elegant way to access nested data from branches of the state that are only available part of the time?
I'm new to React and this problem keeps coming up. I have an ok grasp on Redux and using middleware like Thunk & Saga for the API stuff, but I still don't understand the proper way to write components so that they're not trying to grab non-existent state.
For example, when loading a user's profile photo into a header after the user signs in, the URL will be in the redux store as state.user.userData.photos.primary ...if I try to access that location when the user hasn't signed in, userData is still undefined and React will throw an error. So my solution has been to write assignments like:
let profilePhoto = props.user.userData ? props.user.userData.photos.primary : '';
... which seems cumbersome and inefficient, especially since it requires mapping the entire userData object in mapStateToProps only for the sake of accessing a tiny chunk of data.
I wish I could target it earlier. For example, in mapStateToProps:
profilePhoto : state.user.userData.photos.primary || '';
...but that results in "can't access 'photos' of undefined". So what's an efficient/elegant way to access nested data from branches of the state that are only available part of the time?
In your mapStateToProps access your state like this:
profilePhoto : state.user.userData ? state.user.userData.photos.primary : ''
and then use profilePhoto as props. This will not throw an error of undefined.
In my current application I initialize the redux state with all the data tree that I will use; so that I will unlikely end up with undefineds.
So a solution would be:
const initialState = {
userData: {
photos: {
primary: ""
}
}
}
This might look ugly in the first glance but it helps me keep track of what my data will look like, and can be simplified by defining parts of the object individually.
An alternative could be keeping a flag in the redux state like isUserPrimaryPhotoDefined which you would set to true when you are setting the primary variable; so you can use the flag in the ternary. However that might not guarantee getting rid of the possible errors completely.
With ES6 Destructuring Assignment, you can access nested keys from props in a cleaner way.
const { user: { userData: { photos: { primary = ''} = {}} = {}} = {}} = props;
console.log(primary); // prints primary value or '' if not defined

Resources