Search code examples
javascriptreactjsreact-state-managementreact-state

Unable to save state for first click


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}>&times;</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}>&gt;&gt;</button>
                <button onClick={this.moveRight}>&gt;</button>
                <button onClick={this.moveLeft}>&lt;</button>
                <button onClick={this.moveAllLeft}>&lt;&lt;</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.

Codesandbox

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.


Solution

  • 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
        });
      };