How to update array within state object with react/redux - reactjs

In my reducer i have this state:
const initialState = fromJS({
resources: false,
testval: [0, 0, 0, 0],})
I want to append or remove array values for testval when my action is fired. I have immutable, and immutability-helper imported but im struggling with the syntax to get this to work.
For example this isnt working - the state isnt changing:
return update(state, {
testval: {
$set: 'test'
}
});

It looks like you might be mixing up two different toolsets. Immutable.js is a specialized set of data structures, while the Immutability-Helper library expects to work on plain JS objects.
You might want to read through the Immutable Update Patterns section of the Redux docs, which discusses ways to properly do immutable updates on plain JS data, as well as the related links in the Prerequisite Concepts docs page.

Ok i figured this out using Immutable.
Add to array:
return state.updateIn(['showtask'], arr => arr.push(action.userid))
Remove from array:
return state.updateIn(['showtask'], arr => arr.splice(arr.indexOf(action.userid),1))

its because you are updating it wrong way,
1st thing is your array will be converted to List object, so that need to be updated in list style .
in below example 1 is the index i am updating, you can use any dynamic variable for this, also item is the current value at that index. if that is object(in some other use case) you can modify the object and return it.
const initialState = Immutable.fromJS({
resources: false,
testval: [0, 0, 0, 0]});
//replace initial state with state, i just posted example code
initialState = initialState.set("testval",initialState.get("testval").update(1,(item)=>{
return 2;
}));
console.log(initialState.get("testval"))
for more detailed example you can also check this post https://onl9class.com/using-immutable-js/

Related

Why does slice push work in redux but not concat?

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

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"

update nested Array state reactjs

Unable to handle nested array in react state, here what I am trying I need to push value in this.state.form.contentFormArr
let langulageform, contentObj,contentFormArr;
contentObj={heading:'',subheading:''};
contentFormArr=[this.contentObj]
langulageform =[
{key:"Ar",lang:"Arabic",contentFormArr:this.contentFormArr},
{key:"En",lang:"English",contentFormArr:this.contentFormArr},
{key:"Ru",lang:"Russian",contentFormArr:this.contentFormArr},
{key:"Sp",lang:"Spanish",contentFormArr:this.contentFormArr},
{key:"Ve",lang:"Vetnamese",contentFormArr:this.contentFormArr}
];
constructor(){
super()
this.addContentArea = this.addContentArea.bind(this)
this.state={
form:this.langulageform
}
}
addContentArea(index){
this.setState((state)=>{
const contentformArr = [...state.form[index].contentFormArr,this.contentObj]
return{
...state.form.contentFormArr,
contentformArr
}
})
}
I made multiple forms by the iterating this.state.form array, Now I need when the user clicks on any form box button to add more field then it will push value in particular index array and then it will iterate more fields.
Well I did it but I think the way which I use is not good practice. First I made the correction on Array which I set to state as the initial value, because fore I was set array value to contentForm array when I made changes to them it set changes to everywhere on state Object.
langulageform =[
{key:"Ar",lang:"Arabic",contentFormArr:[{heading:'',subheading:''}]},
{key:"En",lang:"English",contentFormArr:[{heading:'',subheading:''}]},
{key:"Ru",lang:"Russian",contentFormArr:[{heading:'',subheading:''}]},
{key:"Sp",lang:"Spanish",contentFormArr:[{heading:'',subheading:''}]},
{key:"Ve",lang:"Vetnamese",contentFormArr:[{heading:'',subheading:''}]}
];
Secondly I create one blank array and push all values in it, so that I can push value in it, This can't be done in state because of immutable object, and after getting correct index object I made corrections and return the object.
addContentArea(index){
this.setState((state)=>{
let Arr=[];
state.form.map((e,i)=>{
Arr.push(e);
if(i===index){
Arr.contentFormArr.push({})
}
})
return{
Arr
}
})
}
I did it because I feel to deal with multidimensional immutable arrays are bit tricky this is not an optimized way but it works every time and I have checked that the react component lifecycle methods are also working fine with this.

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

Reactjs submitHandler if the state is set

I am new to reactjs - I've tried setting the state if checkbox get selected - and when I read the state I can see the state gets populated console.log('state' ,state) - gives {'option1': true} -- but when I do this.state.length its undefined.
Reason is very simple on: State is an object that's why length is giving undefined.
If you want to check how many key-value pairs are present then use Object.keys, it will return an array of all the keys then use length on that.
Like this:
console.log(Object.keys(this.state).length).
Check this snippet:
let obj = {'option1': true, 'option2': true, 'option3': true};
console.log(Object.keys(obj).length);

Resources