Unwanted modification in State variable (Hook), due to manipulation of local variable - javascript

import React, { useState, useEffect } from "react";
export const Test = () => {
const [data, setData] = useState([]);
const file = [{ id: 1, description: "Test Data" }];
useEffect(() => setData(file), []);
const manipulateData = (data) => {
let tempArray = [...data];
tempArray.map((item) => delete item.id);
};
return (
<>
<h1>Testing Data</h1>
{manipulateData(data)}
{console.log(data)}
</>
);
};
I want to make modification to the local variable "tempArray", and the changes shall not reflect in the variable "data"

This happens because you are creating a new array, but the objects in that array are not deeply copied. Instead, they represent just a reference to the very same objects as in data.
Because both references are pointing at the same object, if you delete an object property from the tempArray objects, you are actually deleting it from the data objects.
If you want to solve this, you have to destroy the reference by making a deep copy of the objects from the data array.
Here is an example how to do it:
export const Test = () => {
const [data, setData] = useState([{ id: 1, description: "Test Data" }]);
const manipulateData = (data) => {
let tempArray = data.map((item) => {item.description}); //this will create an array with new object, and destroy the reference to the objects of data.
};
return (
<>
<h1>Testing Data</h1>
{manipulateData(data)}
{console.log(data)}
</>
);
};
Also, instead of deleting a property, better pick the one that you need.

The following code:
let tempArray = [...data];
only shallow copies your data array, meaning that any of the objects inside of tempArray refer to the same objects in memory that are within the data array. This means that when you loop over your objects with .map(), each item refers to an object from data as well, which results in the objects in data changing when you use delete item.id.
Currently, you are using .map() just to loop over your tempArray, this isn't the purpose of .map(). The .map() method returns a new array, and the callback function you pass it should return a new value that you want to map the current value to. This means you can use .map() to create tempArray, by making the callback function return a new object that excludes the id property:
const manipulateData = (data) => {
const tempArray = data.map(({id, ...rest}) => rest);
// ... do what you need with `tempArray` ...
};
Above, {id, ...rest} is destructuring assignment syntax, which extracts the id property from the current element, and stores all other properties/values from the current object in rest. This allows you to create a new object that excludes the id property.
As a side note, you shouldn't be calling methods such as console.log() and manipulateData() inside of JSX if they don't return anything meaningful that will be rendered to the screen, instead, you can put these in a useEffect() hook.

const [data, setData] = useState([{ id: 1, description: "Test Data" }]);
try this

Your way is right in case you are not using a multi-dimensional array, but here you are cloning a multi-dimensional array,
and by using the = operator, it’ll assign objects/arrays by reference instead of by value! That means whenever you modify any of the two objects it's going to affect the value in both.
A good way to solve this is by creating a deep clone by doing something like this
const tempArray = JSON.parse(JSON.stringify([...data]))
tempArray.map((item) => delete item.id)

Related

How to access values inside a multiple array JSON object?

How can I access array data inside my json object data?
const [data, setData] = useState([])
const getData = () => {
axiosInstance
.get(url + slug)
.then(result => setData(result.data))
}
useEffect(() => {
getData()
}, [])
Here are something I've tried:
console.log(data['symbol'])
console.log(data[0]['symbol'])
console.log(data[0].symbol)
Here is the data being used in my state:
To keep it simple, lets say I want to the access first array, and console log all values for symbol. How would I go about doing that?
Your object data looks like an array of arrays of objects.
This should work:
data[0][0].symbol
To log all the symbols of the first array:
console.log(data[0].map(item => item.symbol))
To access the first array you can use this data[0] to map through all the objects inside the first array and log them you do the following
data[0].map(item => console.log(item.symbol))

Redux UI wont update when changing state of nested array

I'm fairly new to Redux.
My Web application is an eCommerce website where users have multiple carts (each with an id and name) and can place different items in each cart (inside an items array).
When a user deletes an item from the cart, my console.log() statements show that the carts array is being updated in the store, however the User interface doesn't reflect that unless i insert a new cart object inside of the carts array.
Why can't i update a nested array the same way i update a normal array in the store?
How do i fix this?
My initial Store
const intialStore = {
carts: [],
first_name : "ford",
last_name : "doly"
}
My Reducer Function
export default function reducer (store = intialStore, action) {
let {type, payload} = action;
switch(type) {
case DELETE_ITEM_IN_A_CART : {
let carts = [...store.carts]
let newCarts = carts.map((cartItem, index) => {
if (index == payload.cartIndex){
let array = [...cartItem.items]
array.splice(payload.itemIndex, 1)
cartItem.items = [...array ]
}
return cartItem ;
})
console.log(carts)
//carts[payload.cartIndex].items.splice(payload.itemIndex, 1)
return {...store, carts : newCarts}
}
default:
return {...store}
}
My Action Creator
export const deleteitemInCart = (cartIndex, itemIndex) => {
return {
type: DELETE_ITEM_IN_A_CART,
payload: {
cartIndex,
itemIndex
}
}
}
When you use the spread operator, you're only making a shallow copy. In your reducer, try using JSON.parse(JSON.stringify(carts)) in order to make a deep copy before performing the splice operation. This would ensure you are not mutating any existing state, but operating on a copy and returning a new copy at the end of the reducer.
map returns a new array, it does not mutate the original (which is good, we don't want to mutate it). But this means in order for your mapping actions to be persisted, you need to assign the result to a variable.
let newCarts = carts.map(...)
...
return {...store, carts: newCarts}
Right now, you're just returning the same carts array as the new state.

Do I need to use the spread operator when using useState hook on object when updating?

I just started learning about hooks, and according to the official docs on Using Multiple State Variables, we find the following line:
However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
So, if I understand correctly, this mean I don't need to use the spread operator for updating the state?
You still don't want to mutate state. So if your state is an object, you'll want to create a new object and set with that. This may involve spreading the old state. For example:
const [person, setPerson] = useState({ name: 'alice', age: 30 });
const onClick = () => {
// Do this:
setPerson(prevPerson => {
return {
...prevPerson,
age: prevPerson.age + 1
}
})
// Not this:
//setPerson(prevPerson => {
// prevPerson.age++;
// return prevPerson;
//});
}
That said, using hooks you often no longer need your state to be an object, and can instead use useState multiple times. If you're not using objects or arrays, then copying is not needed, so spreading is also not needed.
const [name, setName] = useState('alice');
const [age, setAge] = useState(30);
const onClick = () => {
setAge(prevAge => prevAge + 1);
}
What it means is that if you define a state variable like this:
const [myThings, changeMyThings] = useState({cats: 'yes', strings: 'yellow', pizza: true })
Then you do something like changeMyThings({ cats: 'no' }), the resulting state object will just be { cats: 'no' }. The new value is not merged into the old one, it is just replaced. If you want to maintain the whole state object, you would want to use the spread operator:
changeMyThings({ ...myThings, cats: 'no' })
This would give you your original state object and only update the one thing you changed.

Add key/value pair to existing array of objects

I have an array of objects that is saved into a userList useState which is composed of:
[{
firstName: "blah"
lastName: "blah2"
}
{
firstName: "test"
lastName: "test2"
}]
I have a useEffect that calls a function and returns a value. I want to store a new key and value to each user in userList.
useEffect(() => {
userList.forEach((user, index) =>
returnNewValueForNewKeyFunction(user, index).then(newValue => {
userList[index]['newKey'] = newValue
//this console.log shows new field and value
console.log(userList)
//this console.log ALSO shows new field and value
console.log(JSON.stringify(contactList[index]))
})
)
}
}, [])
This is fine if I'm operating out of console.log, but unfortunately I need to render the data onto the page.. in my render I have:
return (
<TableBody>
{userList
.map((user, index) => (
<TableRow>
<TableCell>
{user.newKey}
</TableCell>
)
user.newKey is showing as blank and it seems like the user wasn't updated at all. How can I make it so the value is actually updated and can be read from when rendering?
You shouldnt mutate your list, you should use useState to store your list, so something like this :
const [ state, setState] = useState(userList);
Then when you want to update, do something like this :
const listCopy = [...state];
//Logic to update your list here
listCopy[index][otherindex] = Value;
setState(listCopy)
Hope this helps
You are modifying your userList but not calling your set function on which means React won't know to re-render with the updated state.
Instead of mutating the current state, you should create a new array and then call the set function returned by useState with the updated array after making your changes.
It also looks like your returnNewValueForNewKeyFunction is a promise / async which means each of your item changes are happening async. You'll need to make these synchronous / wait for them all before updating your state to make your state change a single update for the UI.
E.g., putting these both together - if you are doing:
const [userList, setUserList] = useState();
You could do:
useEffect(() => {
// Since can't use an async func directly with useEffect -
// define an async func to handle your updates and call it within the useEffect func
const updateUsers = async () => {
// Create a new array for your updated state
const updatedUserList = [];
// Loop over your values inline so your can await results to make them sync
for (let index = 0; index < userList.length; index ++) {
const user = userList[index];
const newVal = await returnNewValueForNewKeyFunction(user, index);
// Create a shallow copy of the original value and add the newValue
updatedUserList[index] = { ...user, newKey: newValue };
// ... Any other logic you need
}
// Call set with the updated value so React knows to re-render
setUserList(updatedUserList);
};
// Trigger your async update
updateUsers();
}, [])

Convert object to array in Javascript / React

Using REST, I am retrieving an object(JSON format) which is to be converted to an array so that it can be inserted into a table.
This is done in the rendering function of React.
The input is updated every N minutes from the back-end.
How do I convert an object to an array?
I need only the values, not the keys, since the keys are already present as column values beforehand in the table itself.
You can use Object#values (ECMAScript 2017), but it's not supported by IE (see browser's compatibility).
Note: The ECMAScript 6 specification defines in which order the properties of an object should be traversed. This blog post explains the details.
const map = { a: 1, b: 2, c: 3 };
const result = Object.values(map);
console.log(result);
If you need to support IE, you can use Object#keys with Array#map:
const map = { a: 1, b: 2, c: 3 };
const result = Object.keys(map).map((key) => map[key]);
console.log(result);
I am not sure by map you mean the Map object or an ordinary JS object. However, just for variety I would like to mention that the Map objects are mostly (probably always) stringified like JSON.stringify([...myMap]). So if you happen to receive a Map object in JSON data may be you should do something like;
var myMap = new Map().set(1,"hey").set(2,"you"),
mapData = JSON.stringify([...myMap]),
values = JSON.parse(mapData).map(d => d[1]);
console.log("mapData:",mapData);
console.log("values:",values);
You can set initial value as array firstly. See this example:
const [conversations, setConversations] = useState([]); // fianal data is array
useEffect(() => {
const fetchConversations = async () => {
const res = await axios.get("/conversations/" + user._id);
setConversations(res.data);
};
fetchConversations();
}, [user._id]);
res.data convert to array by using useState([]) as initial value and convert to object by using useState({}).
And
return(
{conversations.map((conv) => (
))}
)
You can set initial value as array firstly. See this example:
const [conversations, setConversations] = useState([]); // fianal data is array
useEffect(() => {
const fetchConversations = async () => {
const res = await axios.get("/conversations/" + user._id);
setConversations(res.data);
};
fetchConversations();
}, [user._id]);
res.data convert to array by using useState([]) as initial value and convert to object by using useState({}).
And map this array:
return (
<>
{conversations.map((conv) =>
(<Conversations key={conv._id} conversation={conv} />))}
</>
)

Categories

Resources