How to update or add an array in react.js? - javascript

I'm using react native and my component is based on class base function. I'm facing difficulty in updating or adding object in an array..
My case :
I have an array:
this.state = {
dayDeatil:[]
}
now i want to add an obj in it but before that i want check if that object exist or not.
obj = { partition :1, day:"sunday","start_time","close_time",full_day:false}
in condition i will check partition and day if they both not match. then add an object if exist then update.
here is function in which i'm trying to do that thing.
setTimeFunc =(time)=>{
try{
console.log("time.stringify() ")
let obj = {
partition:this.state.activePartition,
day:this.state.selectedDay.name,
full_day:false,
start_time:this.state.key==="start_time"?time.toString():null
close_time:this.state.key==="close_time"?time.toString():null
}
let day = this.state.dayDetails.filter((item)=>item.day===obj.day&&item.partition===obj.partition)
if (day.length!==0) {
day[this.state.key]=time.toString()
this.setState({...this.state.dayDetail,day})
} else {
console.log("2")
this.setState({
dayDetails: [...this.state.dayDetails, obj]
})
}
this.setState({ ...this.state, clockVisiblity: false });
}
catch(e){
console.log("error -> ",e)
}
}

To check if the object exists or not, you can use Array.find() method, if it doesn't exists, the method will return undefined.
Now to update the state, the easier way would be to create a new array and set it as the new dayDetails state like this:
const { dayDetails } = this.state;
dayDetails.push(newData);
this.setState({ dayDetails: [...dayDetails] })
You should use the spread operator when setting the state because React uses shallow comparision when comparing states for a component update, so when you do [...dayDetails] you're creating a new reference for the array that will be on the state, and when React compares the oldArray === newArray, it will change the UI.
Also, after your else statement, you're updating the state with the state itself, it's good to remember that React state updates are asynchronous, so they won't be availabe right after the setState function call, this may be causing you bugs too.

Related

Why doesn't custom hook return updated data?

There is a custom useMarkers hook for converting markers into clusters on the map.
It takes needed props,but the essential one are options prop.
const { markersArray, supercluster } = useMarkers({
points: transformedMarkers,
bounds,
zoom,
options: defaultOptions2, // <=== HERE IS WHAT WE NEED
});
And here is it from the inside:
const initialMarkersData = {
markersArray: [],
supercluster: []
};
const useMarkers = (initialData = {}) => {
const [markersData, setMarkersData] = useState(initialMarkersData);
const { clusters, supercluster } = useSupercluster(initialData);
useEffect(() => {
if (initialData.zoom <= 17) {
// console.log("SET NON-CLUSTERS");
setMarkersData({
...markersData,
markersArray: initialData.points,
});
} else {
// console.log("SET CLUSTERS");
setMarkersData({
markersArray: clusters,
supercluster,
});
}
}, [initialData.zoom, clusters, supercluster]);
return markersData;
};
Inside useMarkers I`m passing props to another hook, useSupercluster (comes from separate npm library), and depending on options prop it should return an updated clusters array, and depending on the zoom level an additional useEffect should change the markersData, which is then returns as a result.
And the problem is that when I add a condition for passing different options, the new array is not returned.
For example, if I pass options like this, it works:
options: defaultOptions2,
But if I add condition instead of just single defaultOptions2
options: zoom < 17 ? defaultOptions : defaultOptions2
It doesn't work. If I console.log initialData inside useMarkers hook, I will see new options, so the condition works correct, but although this hook is re-renders, it looks like useSupercluster() doesn`t reinitialize again because it doesn't return modified array according to new options. What could be the reason?
UPDATE*: I tried to investigate this behavior with debugger and everything looks correct. The supercluster property, which is destructured from useSupercluster hook, has to have the this options in it, and although new options successfully passed to useSupercluster within initialData, supercluster property got the empty object at the end;

How to make props equal to state that does not exist yet?

Solved Thank you for your help
I am setting props of component
<Component myprops={state_variable}/>
The problem is that when I am creating the component and setting the props the state variable does not exist yet and my code breaks. What can I do to solve this problem? In addition when I change the state the prop is not updated.
<ServiceTicket
showOverlay={Tickets_disabled_withError[ticket_num]?.isDisabled}
showSelectedError={Tickets_disabled_withError[ticket_num]?.showError}
/>
My intial state initial variable:
const [Tickets_disabled_withError,setTickets_disabled_withError] = useState({})
I am trying to call function that will update state and change value that props is equal to.
const OverLayOnAll = (enable) =>
{
let tempobject = Tickets_disabled_withError
for (let key in tempobject)
{
if (enable == "true")
{
tempobject[key].isDisabled = true
}
else if (enable == "false")
{
tempobject[key].isDisabled = false
}
}
setTickets_disabled_withError(tempobject)
}
I fixed the issue. Thank you so much for your help. I had to set use optional chaining ?. and also re render the component.
The value exists. It's just that the value itself is undefined. You need to set an initial value when defining your state
const [statevariable, setstatevariable] = useState({
somekey: {
isDisabled: false // or whatever the initial value should be
}
}) // or whatever else you need it to be
For your second problem, you are using the same pointer. JavaScript does equality by reference. You've transformed the existing value, so React doesn't detect a change. The easiest way to fix this is to create a shallow copy before you start transforming
let tempobject = {...Tickets_disabled_withError}
Your question isn't very clear to me, but there's a problem in your setTickets_disabled_withError call.
When you update a state property (ticketsDisabledWithError) using its previous value, you need to use the callback argument.
(See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)
overlayAll = (enable)=> {
setTicketsDisabledWithError((ticketsDisabledWithError)=> {
return Object.keys(ticketsDisabledWithError).reduce((acc,key)=> {
acc[key].isDisabled = (enabled=="true");
return acc;
}, {}); // initial value of reduce acc is empty object
})
}
Also, please learn JS variable naming conventions. It'll help both you, and those who try to help you.

Redux how to update nested state object immutably without useless shallow copies?

Im using NGRX store on angular project.
This is the state type:
export interface TagsMap {
[key:number]: { // lets call this key - user id.
[key: number]: number // and this key - tag id.
}
}
So for example:
{5: {1: 1, 2: 2}, 6: {3: 3}}
User 5 has tags: 1,2, and user 6 has tag 3.
I have above 200k keys in this state, and would like to make the updates as efficient as possible.
The operation is to add a tag to all the users in the state.
I tried the best practice approach like so :
const addTagToAllUsers = (state, tagIdToAdd) => {
const userIds = Object.keys(state.userTags);
return userIds.reduce((acc, contactId, index) => {
return {
...acc,
[contactId]: {
...acc[contactId],
[tagIdToAdd]: tagIdToAdd
}
};
}, state.userTags);
};
But unfortunately this makes the browser crush when there are over 200k users and around 5 tags each.
I managed to make it work with this:
const addTagToAllUsers = (state, tagIdToAdd) => {
const stateUserTagsShallowCopy = {...state.userTags};
const userIds = Object.keys(stateUserTags);
for (let i = 0; i <= userIds.length - 1; i++) {
const currUserId = userIds[i];
stateUserTagsShallowCopy[currUserId] = {
...stateUserTagsShallowCopy[currUserId],
[tagIdToAdd]: tagIdToAdd
};
}
return stateUserTagsShallowCopy;
};
And the components are updated from the store nicely without any bugs as far as I checked.
But as written here: Redux website mentions :
The key to updating nested data is that every level of nesting must be copied and updated appropriately
Therefore I wonder if my solution is bad.
Questions:
I Believe I'm still shallow coping all levels in state, am i wrong ?
Is it a bad solution? if so what bugs may it produce that I might be missing ?
Why is it required to update sub nested level state in an immutable manner, if the store selector will still fire because the parent reference indeed changed. (Since it works with shallow checks on the top level.)
What is the best efficient solution ?
Regarding question 3, here is an example of the selector code :
import {createFeatureSelector, createSelector, select} from '#ngrx/store';//ngrx version 10.0.0
//The reducer
const reducers = {
userTags: (state, action) => {
//the reducer function..
}
}
//then on app module:
StoreModule.forRoot(reducers)
//The selector :
const stateToUserTags = createSelector(
createFeatureSelector('userTags'),
(userTags) => {
//this will execute whenever userTags state is updated, as long as it passes the shallow check comparison.
//hence the question why is it required to return a new reference to every nested level object of the state...
return userTags;
}
)
//this.store is NGRX: Store<State>
const tags$: Observable<any> = this.store.pipe(select(stateToUser))
//then in component I use it something like this:
<tagsList tags=[tags$ | async]>
</tagsList>
Your solution is perfectly fine.
The rule of thumb is that you cannot mutate an object/array stored in state.
In Your example, the only thing that You are mutating is the stateUserTagsShallowCopy object (it is not stored inside state since it is a shallow copy of state.userTags).
Sidenote: It is better to use for of here since you don't need to access the index
const addTagToAllUsers = (state, tagIdToAdd) => {
const stateUserTagsShallowCopy = {...state.userTags};
const userIds = Object.keys(stateUserTags);
for (let currUserId of userIds) {
stateUserTagsShallowCopy[currUserId] = {
...stateUserTagsShallowCopy[currUserId],
[tagIdToAdd]: tagIdToAdd
};
}
return stateUserTagsShallowCopy;
};
If you decide to use immer this will look like this
import produce from "immer";
const addTagToAllUsers = (state, tagIdToAdd) => {
const updatedStateUserTags = produce(state.userTags, draft => {
for (let draftTags of Object.values(draft)) {
tags[tagIdToAdd] = tagIdToAdd
}
})
return updatedStateUserTags
});
(this comes usually with performance cost). With immer you can sacrifice performance to gain readability
ad 3.
Why is it required to update sub nested level state in an immutable manner, if the store selector will still fire because the parent reference indeed changed. (Since it works with shallow checks on the top level.)
Every store change selectors recompute to see if the dependent component should re-render.
imagine that instead of immutable update of the user tags we decided to mutate tags inside user (state.userTags is a new object reference but we mutate (reuse) old entries objects state.userTags[userId])
const addTagToAllUsers = (state, tagIdToAdd) => {
const stateUserTagsShallowCopy = {...state.userTags};
const userIds = Object.keys(stateUserTags);
for (let currUserId of userIds) {
stateUserTagsShallowCopy[currUserId][tagIdToAdd] = tagIdToAdd;
}
return stateUserTagsShallowCopy;
};
In your case, you have a selector that takes out state.userTags.
It means that every time a state update happens nrgx will compare the previous result of the selector and the current one (prevUserTags === currUserTags by reference). In our case, we change state.userTags so the component that uses this selector will be refreshed with new userTags.
But imagine other selectors that instead of all userTags will select only one user tags. In our imaginary situation, we mutate directly userTags[someUserId] so the reference remains the same each time. The negative effect here is that subscribing component will be not refreshed (will not see an update after a tag is added).

Getting previous State of useState([{}]) (array of objects)

I am struggling to get the real previous state of my inputs.
I think the real issue Which I have figured out while writing this is my use of const inputsCopy = [...inputs] always thinking that this creates a deep copy and i won't mutate the original array.
const [inputs, setInputs] = useState(store.devices)
store.devices looks like this
devices = [{
name: string,
network: string,
checked: boolean,
...etc
}]
I was trying to use a custom hook for getting the previous value after the inputs change.
I am trying to check if the checked value has switched from true/false so i can not run my autosave feature in a useEffect hook.
function usePrevious<T>(value: T): T | undefined {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref = useRef<T>();
// Store current value in ref
useEffect(() => {
ref.current = value;
}); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
}
I have also tried another custom hook that works like useState but has a third return value for prev state. looked something like this.
const usePrevStateHook = (initial) => {
const [target, setTarget] = useState(initial)
const [prev, setPrev] = useState(initial)
const setPrevValue = (value) => {
if (target !== value){ // I converted them to JSON.stringify() for comparison
setPrev(target)
setTarget(value)
}
}
return [prev, target, setPrevValue]
}
These hooks show the correct prevState after I grab data from the api but any input changes set prev state to the same prop values.
I think my issue lies somewhere with mobx store.devices which i am setting the initial state to or I am having problems not copying/mutating the state somehow.
I have also tried checking what the prevState is in the setState
setInputs(prev => {
console.log(prev)
return inputsCopy
})
After Writing this out I think my issue could be when a value changes on an input and onChange goes to my handleInputChange function I create a copy of the state inputs like
const inputsCopy = [...inputs]
inputsCopy[i][prop] = value
setInputs(inputsCopy)
For some reason I think this creates a deep copy all the time.
I have had hella issues in the past doing this with redux and some other things thinking I am not mutating the original variable.
Cheers to all that reply!
EDIT: Clarification on why I am mutating (not what I intended)
I have a lot of inputs in multiple components for configuring a device settings. The problem is how I setup my onChange functions
<input type="text" value={input.propName} name="propName" onChange={(e) => onInputChange(e, index)} />
const onInputChange = (e, index) => {
const value = e.target.value;
const name = e.target.name;
const inputsCopy = [...inputs]; // problem starts here
inputsCopy[index][name] = value; // Mutated obj!?
setInputs(inputsCopy);
}
that is What I think the source of why my custom prevState hooks are not working. Because I am mutating it.
my AUTOSAVE feature that I want to have the DIFF for to compare prevState with current
const renderCount = useRef(0)
useEffect(() => {
renderCount.current += 1
if (renderCount.current > 1) {
let checked = false
// loop through prevState and currentState for checked value
// if prevState[i].checked !== currentState[i].checked checked = true
if (!checked) {
const autoSave = setTimeout(() => {
// SAVE INPUT DATA TO API
}, 3000)
return () => {
clearTimeout(autoSave)
}
}
}
}, [inputs])
Sorry I had to type this all out from memory. Not at the office.
If I understand your question, you are trying to update state from the previous state value and avoid mutations. const inputsCopy = [...inputs] is only a shallow copy of the array, so the elements still refer back to the previous array.
const inputsCopy = [...inputs] // <-- shallow copy
inputsCopy[i][prop] = value // <-- this is a mutation of the current state!!
setInputs(inputsCopy)
Use a functional state update to access the previous state, and ensure all state, and nested state, is shallow copied in order to avoid the mutations. Use Array.prototype.map to make a shallow copy of the inputs array, using the iterated index to match the specific element you want to update, and then also use the Spread Syntax to make a shallow copy of that element object, then overwrite the [prop] property value.
setInputs(inputs => inputs.map(
(el, index) => index === i
? {
...el,
[prop] = value,
}
: el
);
Though this is a Redux doc, the Immutable Update Patterns documentation is a fantastic explanation and example.
Excerpt:
Updating Nested Objects
The key to updating nested data is that every level of nesting must be
copied and updated appropriately. This is often a difficult concept
for those learning Redux, and there are some specific problems that
frequently occur when trying to update nested objects. These lead to
accidental direct mutation, and should be avoided.

React setState prevState parameter and Array#map

I've encountered a problem with the immutability of the state. Briefly, the Component's state has "persons" array of objects, each of them has a name property, which value I can update on onChange event.
Here is the updater
nameChangedHandler = (event, id) => {
const value = event.target.value;
const persons = this.state.persons.map((pers) => {
if (pers.id === id) {
pers.name = value;
}
return pers;
});
this.setState((prevState) => {
console.log(prevState);
return {
persons
}
});
}
This code works well for me and updates name property of a certain person, but I noticed that this.setState function mutates previous state.
I'm guessing that the reason may be in the map function. But how can it be? As far as I know, map takes the previos array and returns a new one. And this new array is later assigned to the persons property as a whole new array in setState function
.map creates a new array, but the object inside the array are still reference to the state's object.
One approach is to shallow copy the state's object to the new array:
const persons = this.state.persons.map( pers => {
// Do the shallow copy
if (pers.id === id) {
return {
...pers,
name: value
};
}
// Return the reference, because we don't do any mutations
return pers;
});
The above approach would work correctly, only if the Person objects are primitive values. Otherwise, you should do a deep copy.
Also you can check Immutable.js for safer dealing with immutable processing.

Categories

Resources