I've got a reducer that has a data attribute that is an array of objects. That is, basically:
state.data[0] = {id: 1,name: 'joe',tired=true}
state.data[1] = {id: 2,name: 'linda',tired=false}
etc.
I've found that in my reducer, if I want to make linda not tired, I have to dig really deep to force the react "differ" engine recognize a state chage happened. As you can see by the below code, I've practically create a new reference to everything.
Is there a simpler way to do this? I wish I understood how the diff works better so that my object gets rendered when I set the attribute tired to true for a given row. It feels like I'm just thrashing everything.
const idToUpdate = 2;
newState = Object.assign({}, state);
let newData = [];
newState.data.map(function(rec){
if (rec.id === idToUpdate) {
rec.interestLevel = 998;
newData.push(rec);
} else {
newData.push(rec);
}
});
newState.data = newData;
if you know the id you want to update and im assuming you have an array of objects then you can do something like
const {data} = this.state;
const arr = data;
const Linda = arr.filter(item => item.id === idToUpdate)
var TiredLinda = Linda.map(item => return {id:item.id, name:item.name, tired:true}
//Now remove Linda from the original array
arr.filter(item => item.id !== idToUpdate)
//Now we will push the updated Linda to arr to replace the one we removed
arr.push(TiredLinda);
Now you want to set the state of your data
this.setState({data:arr});
Related
in react-native, I am trying to add a simple filtering option on the top of my screen. Just like this one.
But the filter works only on the first hit. After the first, the new array resolves always as empty.
Could anyone tell me where/why is this code failing? Thanks a lot!
import { exercisesList } from '-utils/exercisesList'
const [items, setItems] = useState(exercisesList)
const handleFilter = (treatment = 'All') => {
console.log('FILTER-TREATMENTE---->', treatment)
let filteredList = exercisesList
if (treatment === 'All') {
setItems(exercisesList)
} else {
filteredList = items.filter((item) => item.treatment === treatment)
console.log('filteredList----->', filteredList)
setItems(filteredList)
}
}
I think it is because the second time that the function runs the items has the previous filteted list, not the full list and you are filtering the items array, not exercistsList
I know others answers solve the problem but I think we can expand on the issue a bit just to better understand what went wrong. It was happening because the filtering was being run directly on the state it was supposed to alter so when second run comes its running on previously filtered data that may or may not meet the filtering requirements. Some pseudo code below on how it should have been done
data -> filter(data) -> updateState(filteredData) -> repeat()
const handleFilter = (treatment = 'All') => {
console.log('FILTER-TREATMENTE---->', treatment);
let filteredList = [];
if (treatment === 'All') {
setItems(exercisesList);
} else {
filteredList = exercisesList.filter((item) => item.treatment === treatment);
console.log('filteredList----->', filteredList);
setItems(filteredList);
}
};
i have this code https://stackblitz.com/edit/react-wc2ons?file=src%2FSection.js
I have sections, and i can add items to those sections. How can i delete some item? I tried
const removeItem = (i) => {
setSections((section) => {
const itemTarget = section.items[i];
const filtered = section.items.filter((item) => item !== itemTarget);
return {
...section,
items: filtered,
};
});
};
But for some reason it doesn't work
The removeItem callback prop you pass into the Section component is the way to go and you should get rid of passing setSections down to it as well.
removeItem={(i) => removeItem(index, i)}
Child components shouldn't do parent's work so you had it right at first, I'm going to help you implement that since I can already see the removeItem handler being there in the App component.
removeItem has already all the info you need, I'm going to rename the arguments so it's more clear.
const removeItem = (sectionIndex, index) => {
const newSections = sections.slice();
const newItems = newSections[sectionIndex].items.slice();
newItems.splice(index, 1);
newSections[sectionIndex].items = newItems;
setSections(newSections);
};
Then get rid of removeItem implementation in the Section component and destructure it from the props.
You are using setSections, but you return a single section instead of an array of sections. You probably need something like this:
// using the `section` variable from the upper scope
const removeItem = (i) => {
setSections((sections) => {
const itemTarget = section.items[i];
const filtered = section.items.filter((item) => item !== itemTarget);
const newSections = [...sections];
newSections[section.id] = {
...section,
items: filtered,
};
return newSections;
});
};
A few tips (you don't have to follow them): TypeScript can prevent such mistakes and give useful error messages. Immer.js can make writing such code simpler.
Your problem is that section is an array. So you are currently accessing the undefined property items on it. You would have to change your function to something like this
const removeItem = (i) => {
setSections((section) /* aqui vc tinha chamado de prev*/ => {
const itemTarget = section[i].items[j];
const filtered = section[i].items.filter((item) => item !== itemTarget);
return [...section, {
...section[i],
items: filtered,
}]
});
};
where i is the section in question and j is the item you want to delete.
here is a crude solution to your problem (i noticed other bugs in the code but this solves your issue with removing items at least), but i would separate the sections and items into separate components that in turn has its own states.
There you can add/remove items withing its parent section much more easily.
Now we have to work around this by looking for which section the code wants to remove the current item in.
https://stackblitz.com/edit/react-xxbvp1?file=src%2FSection.js
I am in almost desperate need of help. I am a mechanical engineer and I'm doing a type of calculator for my line of work. I have had an issue I've spent weeks on. I can't seem to solve it.
To not bore you with long code I will try to generalise it as much as possible.
I will first present an example code.
Then I will explain the expected behaviour and what is actually happening for me.
Finally I will explain what I have tried so far to solve this issue.
I will add more content at the bottom based on comments to help clarify my question.
CODE EXAMPLE
THE PARENT OBJECT
import {childObject} from "./childObject"
// in my code "childObject" are actually different from each other
const object1 = Object.assign({}, childObject);
const object2 = Object.assign({}, childObject);
const object3 = Object.assign({}, childObject);
const object4 = Object.assign({}, childObject);
const object5 = Object.assign({}, childObject);
const object6 = Object.assign({}, childObject);
const exampleObject = {
name: "foo",
otherInfo: "bar",
nestedObject:{
standardType: [object1, object2, object3],
specialType: [object4, object5, object6]
},
sumfunc(){}
}
THE CHILD OBJECT
export const childObject = {
name: "I'm losing my mind",
value: "" //<-- this will change
otherInfo: "please help me",
sumfunc(){}
}
EXPLAINING
What I am doing is the following:
Searchbar with all types of parentObjects.
Allowing user to select one or multiple of same or different parentObjects.
Storing the copied selection in a redux store.
Displaying the selection, each parentObject as a form. [see picture]
When typing in form the value of the nested object will change
Now... The issue is when I open the searchbar and select the same parentObject, thus copying it, all its values are mutated. As seen in picture above.
WHAT I HAVE TRIED
I have tried to use lodash clone and deepClone on the selected parentObject.
I have tried to use loads clone and deepClone on the selected childObjects.
I have tried, since the object have the same structure, to go through all key value pairs and shallow copy them.
I have tried to not send the parentObject via the searchbar component to the reducer, instead I just send a string and the reducer itself will add the parentObject to the store.
All methods that I've tried have not stopped the mutation. The deepClone method stopped the mutations, but in return the functions in the objects stopped working (maybe I need to bind it somehow?)
MORE CONTENT
The code that updates the value of the nestedObject
const inputsHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
const formCopy = Object.assign({}, formEQ);
const inputFieldName = e.target.name;
// if anything other than a empty, number or decimal inputted, then return
const isNum = e.target.value.match(/^(?:\d{1,8}(?:\.\d{0,8})?)?$/);
if (!isNum) return;
// Update priority list to calculate the last updated input
formCopy.priorityList = formCopy.priorityList.sort((a, b) => {
if (a === inputFieldName) return 1;
if (b === inputFieldName) return -1;
else return 0;
});
// Update selected input field
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === inputFieldName) {
input.value = e.target.value;
}
return input;
});
// If more than two inputs empty do not calculate
const emptyInputs = formCopy.inputs[calcmode].reduce(
(acc, nV) => (nV.value === "" ? (acc += 1) : acc),
0
);
// Calculate the last edited input field
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === formCopy.priorityList[0] && emptyInputs <= 1) {
const calculatedValue = formCopy.calculate(
formCopy.priorityList[0],
calcmode
);
input.value = Number(calculatedValue).toFixed(2);
}
return input;
});
// Final set hook, now with calculated value
setformEQ({ ...formCopy });
};
Please good people of StackOverFlow... Help me!
Your code has few problems :
you are filtering based on name property of child object and all of them has the same name. Always provide unique id to the objects so that they can be differentiated in easy manner.
Your filter logic is so wrong :
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === inputFieldName) {
input.value = e.target.value; // < -- Culprit
}
return input;
});
Never mutate inline, always create a new copy.
This is how your code change function should be (I have removed dynamic key selection for clarity) :
const change = (e, id) => {
const inputFieldName = e.target.name;
// local copy of array
const nestedArr = [...qform.nestedObject.standardType];
// finding item to be updated
const index = nestedArr.findIndex((i) => i.id === id);
console.log({ index, inputFieldName, e, id });
if (index !== -1) {
const item = nestedArr[index];
item.value = e.target.value;
nestedArr[index] = item;
// deep copy till k depth where k is the key to be updated
const newObject = {
...qform,
nestedObject: {
...qform.nestedObject,
standardType: [...nestedArr],
},
};
setQform(newObject);
}}
Check this Example : Demo
I have a state set as
const [filteredProducts, setFilteredProducts] = useState([]);
I want to be able to append to the end of that state. I am currently trying
products.forEach((product) => {
if (product.category === category) {
setFilteredProducts([...filteredProducts, product]);
}
});
It it looping through the products array correctly. I can even log the product after the setFilteredProducts and it logs the correct ones I want. I am calling this with an onClick.
Find all the products you want to add:
const productsToAdd = products.filter(product => product.category === category)
Then append them
setFilteredProducts((currentFilteredProducts) => ([...currentFilteredProducts, ...productsToAdd]));
The issue with your example is that filteredProducts may get stale after the first iteration. setFilteredProducts will not run synchronously, and filteredProducts keep the original value, until the re-render happen.
You would only append the last match to the existing filteredProducts array.
You can add all matches like so:
setFilteredProducts([...filteredProducts, ...products.filter((product) => product.category === category)]);
I'd recommend you do this in 2 steps:
Create an array of the new products you plan to add
let productsToAdd = [];
products.forEach((product) => {
if (product.category === category) {
productsToAdd.push(product);
}
});
Then combine the arrays and set state
setFilteredProducts([...filteredProducts, ...productsToAdd]);
I think you want what the ES6 built-in function does. You can rewrite your code to give you the the products that match the category like this:
const filteringTheProducts = products.filter(product => {
return product.category === category
})
setFilteredProducts(filteringTheProducts)
The result of the filtering will be the array of all the products that match that criteria.
Here is the documentation for .filter()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
The problem is, that setFilteredProducts doesn't immediately affect products. It's React's job to decide when to update the state. So when you loop over products, you'll probably ending up adding just the last item, because filteredProducts wasn't updated yet.
What you can do, is preparing an array of products to add:
const productsToAdd = products.filter(product => product.category === category);
And then append them:
setFilteredProducts([...products, ...productsToAdd]);
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(item){
item.expand = true;
this.setState({})
}
this.state.myArray.map((item) =>{
return <div onClick={()=>this.clickItem(item)}>{item.name}</div>
})
In React, i have a simple array of objects,
when i click on one of theses object, i want to change their prop and update the state, what is the proper way of doing this.
i feel like there could be a better way
You need to copy your state, update the copied state and the set the state.
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(key){
let items = this.state.myArray;
items[key].expand = true;
this.setState({items})
}
this.state.myArray.map((key, item) =>{
return <div onClick={()=>this.clickItem(key)}>{item.name}</div>
})
Okay, a couple of things.
You're mutating the state directly which is going to fail silently and you're also missing the key prop on your <div.
This is easily resolved though by using the data you have available to you. I don't know whether each name is unique but you can use that as your key. This helps React decide which DOM elements to actually update when state changes.
To update your item in state, you need a way to find it within the state originally, so if name is unique, you can use Array.prototype.find to update it.
clickItem(item) {
const targetIndex = this.state.items.find(stateItem => stateItem.name === item.name)
if (targetIndex === -1)
// Handle not finding the element
const target = this.state.items[targetIndex]
target.expand = !target.expand // Toggle instead of setting so double clicking works as expected.
this.setState({
items: this.state.items.splice(targetIndex, 1, target) // This replaces 1 item in the target array with the new one.
})
}
This will update state and re-render your app. The code is untested but it should work.