I'm building a react component where users can choose between options and once the user hits save after choosing, I'm saving the options in the state. However, it's not getting saved after the first click of saving (which is inside the modal). Once I close the modal and open again then the last chosen options are getting saved.
Sample.js
import React, { Component } from "react";
import Modal from "./Modal";
class Sample extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
usersUn: [
{ id: 1, name: "kepa" },
{ id: 2, name: "rudiger" },
{ id: 3, name: "alonso" },
{ id: 4, name: "Christensen" },
{ id: 7, name: "Kante" },
{ id: 8, name: "Barkley" },
{ id: 9, name: "Tammy" },
{ id: 10, name: "willian" },
{ id: 11, name: "pedro" },
{ id: 12, name: "Loftus-Cheek" },
{ id: 13, name: "Caballero" },
{ id: 15, name: "Zouma" },
{ id: 18, name: "Giroud" },
{ id: 19, name: "Mount" },
{ id: 20, name: "Hudson-Odoi" },
{ id: 22, name: "Pulisic" },
{ id: 23, name: "Batshuayi" },
{ id: 24, name: "James" },
{ id: 28, name: "azpilicueta" },
{ id: 29, name: "tomori" },
{ id: 33, name: "Emerson" }
],
usersSel: [{ id: 17, name: "kovacic" }, { id: 5, name: "jorginho" }],
isLoading: false,
err: null
};
}
save = (itemsLeft, itemsRight) => {
this.setState({
usersUn: itemsLeft,
usersSel: itemsRight
});
};
render() {
return (
<Modal
usersUn={this.state.usersUn}
usersSel={this.state.usersSel}
loading={this.state.isLoading}
err={this.state.err}
title="Gimme a title"
leftTitle="Squad"
rightTitle="PL11"
save={this.save}
/>
);
}
}
export default Sample;
Modal.js
import React, { Component } from "react";
import Popup from "reactjs-popup";
class Modal extends Component {
constructor(props) {
super(props);
this.state = {
open: false,
itemsLeft: this.props.usersUn,
itemsRight: this.props.usersSel,
selectedLeft: [],
selectedRight: [],
filterLeft: "",
filterRight: ""
};
}
openModal = () => {
this.setState({ open: true });
};
closeModal = () => {
this.setState({
open: false,
itemsLeft: this.props.usersUn,
itemsRight: this.props.usersSel
});
};
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
selectLeft = id => {
const selectedVal = this.state.itemsLeft.filter(user => user.id === id);
// console.log(selectedVal);
this.setState(prevState => {
return {
selectedLeft: [...prevState.selectedLeft, ...selectedVal]
};
});
};
selectRight = id => {
const selectedVal = this.state.itemsRight.filter(user => user.id === id);
// console.log(selectedVal);
this.setState(prevState => {
return {
selectedRight: [...prevState.selectedRight, ...selectedVal]
};
});
};
moveAllRight = () => {
this.setState(prevState => {
return {
itemsLeft: [],
itemsRight: [...prevState.itemsRight, ...prevState.itemsLeft]
};
});
};
moveRight = () => {
const updatedItemsLeft = this.state.itemsLeft.filter(item => {
for (var i = 0; i < this.state.selectedLeft.length; i++) {
if (this.state.selectedLeft[i].id === item.id) return false;
}
return true;
});
const updatedItemsRight = [
...this.state.itemsRight,
...this.state.selectedLeft
];
this.setState({
itemsLeft: updatedItemsLeft,
itemsRight: updatedItemsRight,
selectedLeft: []
});
};
moveLeft = () => {
const updatedItemsRight = this.state.itemsRight.filter(item => {
for (var i = 0; i < this.state.selectedRight.length; i++) {
if (this.state.selectedRight[i].id === item.id) return false;
}
return true;
});
const itemsLeft = [...this.state.itemsLeft, ...this.state.selectedRight];
this.setState({
itemsLeft: itemsLeft,
itemsRight: updatedItemsRight,
selectedRight: []
});
};
moveAllLeft = () => {
this.setState(prevState => {
return {
itemsRight: [],
itemsLeft: [...prevState.itemsLeft, ...prevState.itemsRight]
};
});
};
saveList = () => {
this.props.save(this.state.itemsLeft, this.state.itemsRight);
this.closeModal();
};
filterItems = (items, filterTxt) => {
return items.filter(item =>
item.name.toLowerCase().includes(filterTxt.toLowerCase())
);
};
render() {
const { loading, err, title, leftTitle, rightTitle } = this.props;
const { itemsLeft, itemsRight, filterLeft, filterRight } = this.state;
const filteredDataL = this.filterItems(itemsLeft, filterLeft);
const filteredDataR = this.filterItems(itemsRight, filterRight);
if (loading) {
return <div>Loading...</div>;
}
if (err) {
return <div>{err}</div>;
}
return (
<>
<button className="button" onClick={this.openModal}>
Open Modal
</button>
<Popup open={this.state.open} modal closeOnDocumentClick>
<div className="modal__content">
<div className="modal__header">
<h4>{title}</h4>
<button onClick={this.closeModal}>×</button>
</div>
<div className="modal__body">
<div>
<h4>
{leftTitle}
{`(${itemsLeft.length})`}
</h4>
<div>
<input
type="search"
name="filterLeft"
placeholder="search"
value={filterLeft}
onChange={this.handleChange}
/>
</div>
<div className="results">
{filteredDataL.map(user => {
return (
<div
className="list__item"
key={user.name}
onClick={() => this.selectLeft(user.id)}
>
{user.name}
{`(${user.id})`}
</div>
);
})}
</div>
</div>
<div className="controls">
<button onClick={this.moveAllRight}>>></button>
<button onClick={this.moveRight}>></button>
<button onClick={this.moveLeft}><</button>
<button onClick={this.moveAllLeft}><<</button>
</div>
<div>
<h4>
{rightTitle}
{`(${itemsRight.length})`}
</h4>
<div>
<input
type="search"
placeholder="search"
name="filterRight"
value={filterRight}
onChange={this.handleChange}
/>
</div>
<div className="results">
{filteredDataR.map(user => {
return (
<div
className="list__item"
key={user.name}
onClick={() => this.selectRight(user.id)}
>
{user.name}
{`(${user.id})`}
</div>
);
})}
</div>
</div>
</div>
<div className="modal__footer">
<button onClick={this.closeModal}>Cancel</button>
<button onClick={this.saveList}>Save</button>
</div>
</div>
</Popup>
</>
);
}
}
export default Modal;
Can someone please have a look and see what am I missing here? Also, it helps me if there is any better way to do this.
Note: Currently I didn't add any visual experience for the selected item. Once the user clicks on any item it'll be in selected and using > or <
buttons user can move items.
The problem come from the saveList function.
When you call this.props.save
it will execute a setState
in you Sample
parent.
Has setState() is asynchronous, even you call closeModal with the props, the props are not updated with the 'good' values at this time.
Try to set itemLefts
and itemsRight
in the openModal() instead
openModal = () => {
this.setState({
open: true,
itemsLeft: this.props.usersUn,
itemsRight: this.props.usersSel
});
};
closeModal = () => {
this.setState({
open: false
});
};