this.setState isn't making changes in state - reactjs

I am using functions that change a value in a nested object in the state :
an I am calling those functions in a button , they are executed when I click on that button , but one of those functions doesn't make changes to the state
This is the state :
state = {
data: {
attributesLength: this.props.product.attributes.length,
modalMessage: "",
isOpen: false,
},
};
and these are the functions :
addToCart = (id) => {
let data = { ...this.state.data };
if (Object.keys(this.state).length === 1) {
data.modalMessage = "Please, select product attributes";
this.setState({ data});
return;
}
if (
Object.keys(this.state).length - 1 ===
this.state.data.attributesLength
) {
const attributes = Object.entries(this.state).filter(
([key, value]) => key !== "data"
);
if (this.props.cartProducts.length === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
const product = this.props.cartProducts.filter((item) => item.id === id);
if (product.length === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
if (product.length !== 0) {
this.props.changeQuantity({ id: id, case: "increase" });
data.modalMessage = "Quantity increased !";
this.setState({ data });
return;
}
if (this.state.data.attributesLength === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
} else {
data.modalMessage = 'please, select "ALL" product attributes!';
this.setState({ data });
}
};
changeModalBoolean = () => {
let data = { ...this.state.data };
data.isOpen = !data.isOpen;
this.setState({ data });
};
and this is where I am calling functions :
<button
className={product.inStock ? null : "disabled"}
disabled={product.inStock ? false : true}
onClick={() => {
this.addToCart(product.id);
this.changeModalBoolean();
}}
>
{product.inStock ? "add to cart" : "out of stock"}
</button>
NOTE
changeModalBoolean function works and change state isOpen value,

this.addToCart(product.id);
this.changeModalBoolean();
This code run synchronously one after the other. In every function, you create a copy of previous state let data = { ...this.state.data };
so the this.changeModalBoolean(); just replace state which you set in this.addToCart(product.id); to fix this problem, use this.setState((state) => /*modify state*/)
changeModalBoolean = () => {
this.setState((state) => {
let data = { ...state.data };
data.isOpen = !data.isOpen;
return { data };
})
};
or modify the same object in both functions

Related

Change variable in array of objects in state React

What I wanted to do is whenever I click button to change variable in states array of objects. I did it this way, but is there any easier way to do it?
completeItem = (id) => {
const item = this.state.items.filter(el =>{
return el.id === id;
});
item[0].isCompleted = true;
const state = this.state.items;
const arr = this.state.items.filter(el =>{
if(el.id === id){
state[id-1] = item[0];
}
return state;
});
this.setState({
items:[...arr],
});
}
Try this solution...
completeItem = (id) => {
const items = this.state.items.filter(el =>{
if(el.id === id){
el.isCompleted = true;
}
return el;
});
this.setState({
items : [...items],
});
}
If the solution is helpful please let me know!
May compromise a little performance, but it is more readable.
completeItem = (id) => {
const newList = [...this.state.items]
const index = newList.findIndex(el => el.id === id);
newList[index].isCompleted = true
this.setState({ items: newList });
}
Here is how you can do it:
const items = [{
id: 1,
name: "one",
isCompleted: false
},
{
id: 2,
name: "two",
isCompleted: false
},
{
id: 3,
name: "three",
isCompleted: false
},
{
id: 4,
name: "four",
isCompleted: false
}
]
completeItem = (id) => {
const result = items.map(e => {
if (e.id === id) {
return { ...e,
isCompleted: true
}
} else {
return e;
}
});
console.log(result)
}
completeItem(2)

What is the best way to update object array value in React

My React state:
//...
this.state = {
mylist: [
{
"id": 0,
"trueorfalse": false
},
{
"id": 1,
"trueorfalse": false
}
]
}
//...
I am trying to update the trueorfalse value based on the id
Here is what I did so far but didn't work:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
var TorF = true
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { TorF }) : el))
})
}
I really want to make it dynamic so the trueorfase will be opposite of what it is now:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { /* if already true set to false or vice versa */ }) : el))
})
}
How can I update my code to have the dynamicity shown in the second example (if possible), otherwise the first example would do just fine
Another solution using map:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mylist: [
{
id: 0,
trueorfalse: false
},
{
id: 1,
trueorfalse: true
}
]
};
}
toggleBoolean = () => {
const ID = Number(this.state.selectedID);
this.setState(prevState => ({
mylist: prevState.mylist.map(item => {
if (item.id === ID) {
return { ...item, trueorfalse: !item.trueorfalse };
} else {
return item;
}
})
}));
};
render() {
return (
<div className="App">
<p>{`State values: ${JSON.stringify(this.state.mylist)}`}</p>
<button onClick={this.toggleBoolean}>Change true/false values</button>
<label>Insert ID:</label>
<input
type="number"
onChange={event => this.setState({ selectedID: event.target.value })}
/>
</div>
);
}
}
I think the following code would accomplish your second question.
var idnum = e.target.id.toString().split("_")[1]
let newList = Array.from(this.state.mylist) //create new array so we don't modify state directly
if (type === 1) {
let objToUpdate = newList.find((el) => el.id === idnum) // grab first element with matching id
objToUpdate.trueorfalse = !objToUpdate.trueorfalse
this.setState( { mylist: newList } )
}

How to set value for Field at state parent from child component

I have 2 Components: Community.js and Edit.js
I call to Edit from Community below:
<DetailModal
DetailModal={Detail}
errors={this.state.errors}
uploadFile={this.props.uploadFileActions.uploadFile}
onSave={this.save}
onChange={this.onChange}
mode={this.state.mode}
data={this.state.details}
isOpen={this.state.modalIsOpen}
closeModal={this.closeModal}
editable={isHasEditPermisson}
/>
At Community, I have a function onchange() below:
onChange = (field, data) => {
let value = null;
if (data) {
value = data
}
this.setState(state => ({
details: {
...state.details,
[field]: value
},
errors: {
...state.errors,
[field]: undefined
}
}));
// }
}
At Edit, I have a function which called to select image/video file:
selectFile = (file) => {
if (file && file.target.files.length > 0) {
const checkType = file.target.files[0].type.split('/')[0]
const extendType = file.target.files[0].type.split('/')[1]
const fileArr = [];
// if (checkType === "video") {
// console.log('this.getDuration(file)', this.getDuration(file))
// if (this.getDuration(file) > 60) {
// alert("stop");
// return;
// }
// }
// this.props.uploadFile(file.target.files[0], (res) => {
// this.props.onChange('ResourceUrl', `${this.props.data.ResourceUrl ? `${this.props.data.ResourceUrl};` : ''}${res.data.Data}`);
// });
fileArr.push({
file: file.target.files[0],
urlFile: URL.createObjectURL(file.target.files[0]),
});
this.props.onChange('ResourceUrl', `${this.props.data.ResourceUrl ? `${this.props.data.ResourceUrl};` : ''}${fileArr[0].urlFile}`);
this.props.onChange('ResourceFile', this.props.data.ResourceFile ? this.props.data.ResourceFile : fileArr[0].file);
if (checkType === "image") {
this.setState({
fileType: "image/*",
extend: extendType
})
} else {
this.setState({
fileType: "video/*",
countVideo: 1,
extend: extendType
})
}
// file.target.value = '';
}
}
This is Init state in Community:
constructor(props) {
super(props);
this.escFunction = this.escFunction.bind(this);
this.state = {
modalIsOpen: false,
mode: 'add',
details: {},
errors: {},
defaultRole: constants.CollaboratorRole.default,
permanentRole: constants.CollaboratorRole.permanent,
isOpenDeleteConfirm: false
};
}
Here, I call to onchange() in Community to set value for 2 field: ResourceUrl, ResourceFile
But I have an issue when set value for ResourceFile. When I choose second file then I still get value of first file.
I don't know how to set the value of the second file into ResourceFile, which means that I expect that ResourceFile is an array containing the information of the two files I just selected.

Input field unselects when I try and update

I'm trying to update the input value on a form. The input value I need to update sits within an array of objects within another array of objects. I'm trying to update the address within emails (see below).
const userProfiles = [{
firstName: 'John',
emails: [{ address: 'john#gmail.com' }]
}]
Each keystroke updates the field and sate of the userProfiles, however, the input field disengages. So I have to keep reselecting the input field. What am I missing here?
handleInputChange = (userProfileId, index) => (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
const userProfiles = this.state.userProfiles.map((userProfile) => {
if (userProfile._id === userProfileId) {
if (name === 'email') {
const emails = userProfile.emails.map((email, idx) => {
if (idx === index) {
return {
...email,
address: value,
};
}
return {
...email,
};
});
return {
...userProfile,
emails,
};
}
return {
...userProfile,
[name]: value,
};
}
return {
...userProfile,
};
});
this.setState({
userProfiles,
});
}
handleInputChange = (userProfileId, index) => (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
let { userProfiles } = this.state;
userProfiles.map((eachProfile) => {
let { emails } = userProfiles.emails;
if (userProfile._id === userProfileId) {
if(name === 'email') {
emails.map((emails, idx) => {
if (idx === index) {
emails = value;
}
})
}
}
});
this.setState({
...this.state,
userProfiles
})
}
Can you try this one?

Filter out existing objects in an array of objects

I don't think this is difficult, I just can't figure out the best way to do it. This function is creating an array, from a group of checkboxes. I then want to break up the array and create an array of objects, because each object can have corresponding data. How do I filter out existing rolesInterestedIn.roleType.
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray;
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
}
this.setState({ typeOfWork: newSelectionArray }, function() {
this.state.typeOfWork.map((type) => {
this.setState({
rolesInterestedIn: this.state.rolesInterestedIn.concat([
{
roleType: type,
}
])
}, function() {
console.log(this.state.rolesInterestedIn);
});
})
});
}
UDPATE
rolesInterestedIn: [
{
roleType: '',
experienceYears: ''
}
],
Because each time you do setState you are concatenating the new value to the prev one in rolesInterestedIn array. Add new value only when you are adding new item, otherwise remove the object from both the state variable typeOfWork and rolesInterestedIn.
Try this:
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray, rolesInterestedIn = this.state.rolesInterestedIn.slice(0);
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection);
rolesInterestedIn = rolesInterestedIn.filter(s => s.roleType !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
rolesInterestedIn = newSelectionArray.map((workType) => {
return {
roleType: workType,
experienceYears: '',
}
});
}
this.setState({
typeOfWork: newSelectionArray,
rolesInterestedIn: rolesInterestedIn
});
}
Suggestion: Don't use multiple setState within a function, do all the calculation then use setState once to update all the values in the last.

Resources