update array of object without mutation - javascript

I'm following a react tutorial but I'm lost. I don't understand starting line 9.
so I tried to make a little miarature
const updateTodo = (list, updated) => {
const index = list.findIndex(item => item.id === updated.id)
return [
...list.slice(0,index),
updated,
...list.slice(index+1)
]
}
https://jsbin.com/sifihocija/2/edit?js,console but failed to produce the result that the author did, what's wrong?

Issue is in this line:
const index = list.findIndex(item => item.id === updated.id)
updated is an array, to access the id, you need to specify the index also,
for other array you are using loop, so item will be the each object of the array, and item.id will give you the id of each object, try this:
const index = list.findIndex(item => item.id === updated[0].id)
const arr = [
{id:1,name:'hello'},
{id:2,name:'hai'}
]
const arr2 = [
{id:2,name:'this should be a new string'}
]
const updateTodo = (list, updated) => {
const index = list.findIndex(item => item.id === updated[0].id);
return [
...list.slice(0,index),
...updated,
...list.slice(index+1)
]
}
console.log(JSON.stringify(updateTodo(arr,arr2)))
Check the working code: https://jsbin.com/pazakujava/edit?js,console
Let me know if you need any help in this.

It's simpler and cleaner using Array.map:
const updateTodo = (list, updated) =>
list.map((item) => {
if (item.id !== updated.id) {
return item;
}
return updated;
});

Related

Improve useEffect mutating array objects and get difference beetween

I need help to see if I can improve this code that I have, first I describe the final result that I hope and maybe this way you can find how to improve a little this useEffect:
I need to receive that data object, compare it with one previously stored in localstorage, and compare them to see if there is any new record in that object, if it finds new data, extract them, add the isNew property only to those key that found new and create a new array with the data I received and newArray that includes the modified data.
const localStorageKey = 'laboratoryData'
const oldData = localStorage.getItem(localStorageKey)
useEffect(() => {
if (data) {
if ([null, undefined].includes(oldData)) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data))
} else {
const arrayIsEqual = (data, oldData) => {
data.data === oldData ||
(data.data.length === oldData.length &&
data.data.every(
(f, i) => f.id === oldData[i].id && f.name === oldData[i].name,
))
}
if (arrayIsEqual) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data))
} else {
const diff = data.data
.filter((x) => !oldData.includes(x))
.concat(oldData.filter((x) => !data.data.includes(x)))
const newArray = diff.map((obj) => ({...obj, isNew: 'true'}))
const finalArray = {...data, ...newArray}
localStorage.setItem(localStorageKey, JSON.stringify(finalArray))
}
}
}
}, [data])
Maybe i`m duplicating some code or missing a custom hook with a better performance
Thanks!
without seeing your data and based on your code snippet, hopefully your data.data and oldData are always be arrays of objects that have always have key id and name.
const localStorageKey = 'laboratoryData';
const oldData = localStorage.getItem(localStorageKey);
useEffect(() => {
if (!data) return;
if (!oldData) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data));
return;
}
if (data.data.length === oldData.length && data.data.every((d, i) => d.id === oldData[i].id && d.name === oldData[i].name)) return;
const newData = data.data.filter(d => oldData.findIndex(od => od.id === d.id && od.name === d.name) === -1).map(d => d["isNew"] = true);
const finalArray = oldData.map(od => od["isNew"] = false).concat(newData);
localStorage.setItem(localStorageKey, JSON.stringify(finalArray));
}, [data])

Update array inside object if it exists, otherwise push array to object

If the object is currently in the array I want to update some value, and if it's not I want to add it to the array. This is the solution I have below, which I don't feel is the best/correct way to do it.
const handleAddToCart = product => {
const newList = [...cart]
if (!newList.includes(product)) {
newList.push(product)
} else {
const productIndex = newList.findIndex((obj => obj._id === product._id))
newList[productIndex].prop = "Some value"
}
setCart(newList)
}
Thank you.
You have to be pretty careful here, as there are a few gotchas with object comparison and mutating the state ([...cart] is not sufficient in deep copying cart). You can update the state in a pure fashion as follows, although I would recommend something like Redux for complex state management.
const handleAddToCart = product => {
const index = cart.findIndex(obj => obj._id === product._id)
if (index === -1) {
setCart(cart.concat(product))
} else {
setCart([
...cart.slice(0, index),
Object.assign({}, product, { prop: "Some value" }),
...cart.slice(index + 1),
])
}
}
const handleAddToCart = productToAdd => {
const newList = [...cart];
const existent = newList.find(product => product.id === productToAdd.id);
if(existent) {
existent.prop = "prop";
} else {
newList.push(productToAdd);
}
setCart(newList);
}

ES6 filter in a filter is this possible?

I have an Array that looks like this:
I need the id where slug === 'slug'
let activeFaq = this.faq.filter(obj => obj.items.filter(item => item.slug === 'slug'));
console.log(activeFaq.id);
This gives me an undefined. I should get 2729
There are multiple ways you could go about this. If you can use .flatMap, then one solution would be to flatten all items into a single array and then use .find:
const item = this.faq.flatMap(obj => obj.items).find(item => item.slug === 'slug')
However, flattening seems a bit unnecessary. An approach that avoid unnecessary computations would be a helper function with a boring loop:
function findItem(faqs, callback) {
for (const faq of faqs) {
const item = faq.find(callback);
if (item) {
return item;
}
}
}
const activeFaq = findItem(this.faq, item => item.slug === 'slug');
var activeFaq = this.faq.filter(obj => obj.items.some(item => item.slug === 'slug'))[0];
console.log(activeFaq.items[0].id); //2729
let activeFaq = this.faq.reduce( ( obj ) => {
const foundItem = obj.items.find( ( item ) => {
return item.slug === 'slug'
} )
if( foundItem ) {
acc = foundItem
}
return acc
}, null )
console.log(activeFaq.id);

How to get and show all the data recovered?

I have some datas, who I can get my data when I'm in the loop. But, if I set the data in a useState array value, I just have the last value.
How to get and show all the data in the render ?
My code out :
const [messagesList, setMessagesList] = useState([])
My code at useEffect :
firebase.database().ref(`accounts/${username}/messages/`).on('value', (snapshot) => {
const jsonData = snapshot.toJSON()
const keys = Object.keys(jsonData)
const finalData = keys.map(key => {
const element = jsonData[key]
for (const i in element) {
const elementFinal = element[i]
setMessagesList([elementFinal])
}
})
})
My code at render :
{messagesList.map((x, i) => (
<div key={i}>
<p key={i}>{x.senderUsername} said "{x.message}"</p>
</div>
))}
My Firebase database :
My result :
You are erasing the value of messageList. Let say, elementFinal equals '45'
setMessagesList([elementFinal])
will result in
messagesList // ['45']
You need to read the previous value (the shallow copy is important) and set the new one:
const array = [...messagesList];
array.push(elementFinal);
setMessagesList(array)
EDIT: I didnt see that it could be reduced as follow:
const messages = [];
const finalData = keys.map(key => {
const element = jsonData[key]
for (const i in element) {
messages.push(element[i]);
}
})
setMessagesList(messages);

Remove an array item nested inside of an object

I'm trying to remove a specific item from an objects array based on the title attribute in the array. I keep running into a problem where I can view the array item, but I'm not able to splice the item out of the array based on the parameters entered in my remove function. I'm just getting the error message back from my else statement in the function.
I've tried using find, forEach, findIndex and match that case in order to test out removing the result based on the index, or the text value of the key 'text'. I commented out all of the functions I tried prior to searching for the answer in the forum recommendations. All of my recipe functions are working, along with my createIngredient function, which adds an object to the recipe array. But the removeIngredient function I've been trying to get to work, isn't because of the problems mentioned above.
let recipes = []
// Read existing recipes from localStorage
const loadRecipes = () => {
const recipesJSON = localStorage.getItem('recipes')
try {
return recipesJSON ? JSON.parse(recipesJSON) : []
} catch (e) {
return []
}
}
// Expose recipes from module
const getRecipes = () => recipes
const createRecipe = () => {
const id = uuidv4()
const timestamp = moment().valueOf()
recipes.push({
id: id,
title: '',
body: '',
createdAt: timestamp,
updatedAt: timestamp,
ingredient: []
})
saveRecipes()
return id
}
// Save the recipes to localStorage
const saveRecipes = () => {
localStorage.setItem('recipes', JSON.stringify(recipes))
}
// Remove a recipe from the list
const removeRecipe = (id) => {
const recipeIndex = recipes.findIndex((recipe) => recipe.id === id)
if (recipeIndex > -1) {
recipes.splice(recipeIndex, 1)
saveRecipes()
}
}
// Remove all recipes from the recipe array
const cleanSlate = () => {
recipes = []
saveRecipes()
}
const updateRecipe = (id, updates) => {
const recipe = recipes.find((recipe) => recipe.id === id)
if (!recipe) {
return
}
if (typeof updates.title === 'string') {
recipe.title = updates.title
recipe.updatedAt = moment().valueOf()
}
if (typeof updates.body === 'string') {
recipe.body = updates.body
recipe.updateAt = moment().valueOf()
}
saveRecipes()
return recipe
}
const createIngredient = (id, text) => {
const recipe = recipes.find((recipe) => recipe.id === id)
const newItem = {
text,
have: false
}
recipe.ingredient.push(newItem)
saveRecipes()
}
const removeIngredient = (id) => {
const ingredient = recipes.find((recipe) => recipe.id === id)
console.log(ingredient)
const allIngredients = ingredient.todo.forEach((ingredient) => console.log(ingredient.text))
// const recipeIndex = recipes.find((recipe) => recipe.id === id)
// for (let text of recipeIndex) {
// console.log(recipdeIndex[text])
// }
// Attempt 3
// if (indexOfIngredient === 0) {
// ingredientIndex.splice(index, 1)
// saveRecipes()
// } else {
// console.log('error')
// }
// Attempt 2
// const recipe = recipes.find((recipe) => recipe.id === id)
// const ingredients = recipe.todo
// // let newItem = ingredients.forEach((item) => item)
// if (ingredients.text === 'breadcrumbs') {
// ingredients.splice(ingredients, 1)
// saveRecipes()
// }
// Attempt 1
// const ingredientName = ingredients.forEach((ingredient, index, array) => console.log(ingredient, index, array))
// console.log(ingredientName)
// const recipeIndex = recipes.findIndex((recipe) => recipe.id === id)
// if (recipeIndex > -1) {
// recipes.splice(recipeIndex, 1)
// saveRecipes()
// }
}
recipes = loadRecipes()
OUTPUT
{id: "ef88e013-9510-4b0e-927f-b9a8fc623450", title: "Spaghetti", body: "", createdAt: 1546878594784, updatedAt: 1546878608896, …}
recipes.js:94 breadcrumbs
recipes.js:94 noodles
recipes.js:94 marinara
recipes.js:94 meat
recipes.js:94 ground beef
recipes.js:94 milk
So I'm able to view the output I printed above and see each item in the ingredients array, but trying to splice the item based on the index number or key is not working for me with the functions I have already tried and the info I have found on Stackoverflow about objects, arrays and the splice method so far.
If I am understanding correctly (after reading the commented out attempts in your code), you are trying to remove the "breadcrumbs" ingredient from the recipe that corresponds to the id passed to the removeIngredient() function.
In that case, perhaps you could take a slightly different approach to removing the ingredient from the recipes todo array, via the Array#filter method?
You could use filter() in the following way to "filter out" (ie remove) the "breadcrumbs" ingredient from the todo array via the following filter logic:
// Keep any ingredients that do not match ingredient (ie if ingredient
// equals "breadcrumbs")
todo.filter(todoIngredient => todoIngredient !== ingredient)
You might consider revising your removeIngredient() function by;
adding an additional ingredient parameter to the function arguments. This allows you to specify the ingredient to be removed from the recipe corresponding to recipeId
and, introducing the filter() idea as described:
const removeIngredient = (recipeId, ingredient) => {
const recipe = recipes.find(recipe => recipe.id === recipeId)
if(recipe) {
// Filter recipe.todo by ingredients that do not match
// ingredient argument, and reassign the filtered array
// back to the recipie object we're working with
recipe.todo = recipe.todo.filter(todoIngredient =>
(todoIngredient !== ingredient));
}
}
Now, when you introduce the "remove" button for each ingredient, you would call the removeIngredient() as follows:
var recipeId = /* got id from somewhere */
var ingredientText = /* got ingredient from somewhere */
removeIngredient( recipeId, ingredientText );
Hope this helps!

Categories

Resources