This question already has answers here:
How do I remove a property from a JavaScript object?
(37 answers)
Closed 4 years ago.
So I created React Todo App with an Object as a Todo List. Removing an item by key works with delete, but brings me back boolean instead of Object with the rest of the items.
deleteTodo = (id) => {
const { todos } = this.state;
this.setState({
todos: delete todos[id],
});
I get this in console:
Warning: Failed prop type: Invalid prop `todos` of type `boolean` supplied to `TodoList`, expected an object.
You should use immutable operations when using Redux. You should not change your state directly.
For that, you can use destructuring to exclude your todo from the todos, if your todos are in an object:
const { todos } = this.state;
const { [id]: _, ...newTodos } = todos;
this.setState({
todos: newTodos
});
If the todos are in a list and since you cannot destructure an item by index from an array, use the slice method, which doesn't modify the array, but returns a modified copy:
const { todos } = this.state;
this.setState({
todos: [...todos.slice(0, id), ...todos.slice(id + 1)];
});
You have to firstly copy your state to an array so you have a clone of it.
Then you remove the unwanted id from your new array.
var newTodoArray = this.state;
newTodoArray.remove(id);
this.setState({
todos: newTodoArray,
});
Something like the above.
Related
I have defined an object as follows in a file:
export const items = {
first: false,
second: false,
third: false
}
I'm using it in a component as follows:
import { items } from 'file';
const [elements, setElements] = useState(items);
I have a method which gets called when a button is clicked - the method should change the all the values in elements to true
Using the following changes the values but it does not trigger a re-render of the component (which is what I need)
Object.keys(elements).forEach(element => elements[element] = true);
How can I use setElements to update all the values in elements?
The problem you are facing is that you are mutating the state object, which means that at the end, prevState === nextState and React bails out of rendering. An option is to use a new object and copy the props like this, using the same combo of Object.keys and forEach but adding an extra step:
setState(prevState => {
const nextState = {}
Object.keys(prevState).forEach(key => {
nextState[key] = true
})
return nextState
})
This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I'm trying to update the cars array whenever the Add button is clicked. I see that new object is added to updatedCars array but when I try to set the state with it, the cars state won't get updated.
I still see the initial state even after adding a new object in the array.
export default function App() {
const [cars, setCars] = useState([{ name: "Audi", type: "sedan" }]);
const handleAdd = () => {
const newCar = { name: "Benz", type: "sedan" };
const updatedCars = [...cars, newCar];
console.log("updatedCars", updatedCars);
setCars(updatedCars);
console.log("result", cars);
};
return (
<div className="App">
<button onClick={handleAdd}>Add</button>
</div>
);
}
I created a working example using CodeSandbox. Could anyone please help?
setCars is the asynchronous method, and you can't get the updated value of cars immediately after setCars()
setCars(updatedCars);
console.log(cars); //This will console old value of `cars`
You should use useEffect with adding a cars dependency to check updated value.
useEffect(() => {
console.log(cars);
}, [cars]);
could any React expert here please helps me answer this weird issue I have with my react component?
I am trying to create a function to remove an item in an array, however, the setState does not update new array but console.log shows the array is correctly updated with the items properly removed.
constructor (props) {
super (props);
this.state = {
data: dummyData,
chosenData:[],
chosenOption:{}
}
}
onDeleteOptionButtonClick = (id, titleId) => {
const data = this.state.data.map(item => item.id === titleId ? {...item, possibleAnswers: item.possibleAnswers.filter(i => i.id !== id)} : item)
this.setState({ data }, () => {console.log('log of data from state', this.state.data[0].possibleAnswers)})
console.log('log of data from data',data[0].possibleAnswers)
}
here is the picture of the console.log
as you can see that the const data properly updates new array with the item removed, but the state stays the same.
Please help, thanks
Please Use this.setState inside the filter function itself.
Im wracking my brain trying to understand the component structure here that I should employ. I feel like this is something that I absolutely want to get correctly because going forward it's important to understand how a ReactJS application should look and how to correctly separate the concerns. I know it is an opinionated front but how I am currently doing it is not correct, and I was looking for some insight.
Data Model: This is a large array of Recipes, each contains another array of ingredients. I want to allow the user to "tick off" (the ingredient is removed from the array) the ingredients as they buy them/acquire them.
[
...
{
"title": "Recipe 1"
"ingredients": [
{ "title": "Flour", "measurement": "300g" },
{ "title": "Sesame Seeds", "measurement": "1 Tblsp" }
]
},
...
]
Current Psuedo Component Tree:
// RecipeList is my "HOC" (higher-order component) and contains all the functions/data required for the rest of the tree, and simply passes them down as props. This is `connect()`ed to the store here.
<RecipeList>
// Map on the highest level array to display a <Recipe> component for each
<Recipe data={recipe[mappedIndex]}>
// Map on each ingredient to create a list item for each
<RecipeIngredient data={recipe[mappedIndex].ingredient[mappedIndex]>
<IngredientCheckBox onChange={ //remove this ingredient from the array } />
</RecipeIngredient>
</Recipe>
</RecipeList>
All is well with the above, the data is displayed exactly how I would expect it to.
However, and this is the main issue, when it comes to onChange I call an action, COMPLETE_INGREDIENT which basically removes it from the ingredients array (I see it in action, using redux-logger the next-state does not contain it).
Unfortunately, my components don't rerender. It is no longer in the array but is still displayed on screen. I understand this may one of the following reasons:
connect() only shallow compares the states so doesn't trigger a rerender because it is a value in an array, inside of a property of an object in an array.
My connect() is too far from the action, and should be reconnect()ed at a deeper component level, say the <Recipe> component and only attach it to a part of the store that it cares about (could even be the <RecipeIngredient>).
My reducer is not modifying the state in an immutable way. This is the one I have spent most time on, however even using slice() and the sorts, I still can't get it to re-render
Edit: My reducer for action COMPLETE_INGREDIENT. I understand this may be the issue, as it is directly mutating the state. What would be the correct way for such a deep change to the state?
case COMPLETE_INGREDIENT:
// state is from a level above the recipe's, that contains a timestamp etc
state.recipes[action.payload.recipeIndex].ingredients.splice(action.payload.ingredientIndex, 1)
return Object.assign({
...state
})
Edit: My reducer for action COMPLETE_INGREDIENT. I understand this may
be the issue, as it is directly mutating the state. What would be the
correct way for such a deep change to the state?
Yep, you are mutating state with that Object.assign. As a first argument it should have new Object to copy values to and return:
return Object.assign({}, {
...state
})
Based on your code I've created updating function I would probably create:
case COMPLETE_INGREDIENT: {
const { recipeIndex, ingregientIndex } = action.payload;
const recipesListCopy = [...state.recipes];
const recipeCopy = {
...recipesListCopy[recipeIndex],
ingredients: recipesListCopy[recipeIndex].ingredients.filter(
(e, index) => index !== ingredientIndex
)
};
recipesListCopy[recipeIndex] = recipeCopy;
return {
...state,
recipes: recipesListCopy
};
}
Edit:
based on your comment - "remove the recipe from the top level recipe array if the ingredients array is now empty"
case COMPLETE_INGREDIENT: {
const { recipeIndex, ingregientIndex } = action.payload;
const recipesListCopy = [...state.recipes];
const updatedIngredientsList = recipesListCopy[recipeIndex].ingredients.filter(
(e, index) => index !== ingredientIndex
);
if(updatedIngredientsList.length > 0) {
// update ingredients
const recipeCopy = {
...recipesListCopy[recipeIndex],
ingredients: updatedIngredientsList
};
recipesListCopy[recipeIndex] = recipeCopy;
} else {
// remove recipe because no igridients
recipesListCopy.splice(recipeIndex, 1);
}
return {
...state,
recipes: recipesListCopy
};
}
Problem here is in mutating existing state
case COMPLETE_INGREDIENT:
// state is from a level above the recipe's, that contains a timestamp etc
state.recipes[action.payload.recipeIndex].ingredients.splice(action.payload.ingredientIndex, 1)
return Object.assign({
...state
})
Method splice changes existing array, but you need to create new array. Here is example
const obj = { a: { b: 5 } };
const copyObj = { ...obj };
copyObj.a.b = 1;
console.log(obj.a.b); // 1
console.log(copyObj.a.b); // 1
You have copied state object but ingredients array stays the same. So you need to copy array.
case COMPLETE_INGREDIENT:
return {
...state,
recipes: state.recipes.map(
(item, index) =>
index === action.payload.recipeIndex
? { ...item, ingredients: item.ingredients.filter((item, index) => index !== action.payload.ingredientIndex) }
: item
),
};
I am facing an issue where after I push an object to an array it will be reactive to changes.
// actions.js
export const addToCart = ({ commit }) => {
commit('addToCart'); // properly commits to change the state
setTimeout(function () {
commit('resetToppings');
}, 10000);
};
// mutations.js
export const addToCart = (state) => {
state.cart.push(state.orderItem);
};
export const resetToppings = (state) => {
state.orderItem.toppings = [];
};
// state.js
cart: [],
orderItem: {
quantity: 1,
toppings: [],
},
The orderItem gets properly pushed to the cart, but 10 seconds later when I resetToppings, it resets the toppings inside the cart as well as in the orderItem.
How can I make sure that resetToppings does not mutate anything inside cart?
When you push state.orderItem you add a reference to it in the array. So when state.orderItem changes, the element inside the array changes, because it (the element inside the array) is actually still pointing to the same (state.orderItem) object.
You can push a shallow clone of the orderItem object instead:
// mutations.js
export const addToCart = (state) => {
state.cart.push({...state.orderItem});
};
This way what is added to the array is a different object.
Note: you can do:
state.cart.push({...state.orderItem});
But this will only work if you never remove/add elements from/to the toppings array directly after the addToCart is called. That is, if you call resetToppings before adding new elements to toppings (which will work because resetToppings assigns a new array).
If that's not always the case, I mean, if you sometimes edit the toppings array directly after the addToCart is called, you might want to clone it as well:
state.cart.push(Object.assign({...state.orderItem}, {toppings: [...state.orderItem.toppings]}});