Search code examples
javascriptreactjsreact-hookssetstate

React: Changing State in functional component also Change functional component's props value, and its Parent Class State


I have a Parent Class let say Data and i am Rendering Model with checkbox list if this.state.showList is true and passing this.state.list as props to functional component (see Code)

Data Class Component

import React from "react";
import Model from "./components/Model";

class Data extends React.Component {
  state = {
    list: [
           {name:'ABC', selected: false},
           {name:'DEF', selected: false},
           {name:'GHI', selected: false},
           {name:'JKL', selected: false},
           {name:'MNO', selected: false},
          ],
    showList: false
  };

  toggleModel = () => {
    this.setState({
      showList: !this.state.showList 
    });
  };

  updateState= (list) => {
    this.setState({ list, showList: !this.state.showList });
  }

  render() {
    const model= this.state.showList ? (
      <Model
        Click={this.toggleModel}
        okayClick={this.updateState}
        list={this.state.list}
      />
    ) : null;
    return (
      <div className="container">
        {model}
        // Extra
      </div>
    );
  }
}
export default Data;

Model Functional Component

import React, { useState, useEffect } from "react";
import Button from "./UI/Button/Button";

const FilterModel = (props) => {
  const [state, setState] = useState(props.list);

  const checkboxClick = (index, selected) => {
    const newState = [...state];
    const newElement = newState[index];
    newElement.selected = !selected;
    newState[index] = newElement;
    setState(newState);
  };

  return (  
    <div className="model_container" onClick={() => props.Click("")}>
      <div className="model" onClick={e => e.stopPropagation()}>
        <div className="model_head">
          <span className="title">List</span>
        </div>
        <div className="model_body">
          <ul>
            {state.map((currentElement, index) => (
              <li key={index}>
                <label>
                  <input
                    type="checkbox"
                    onChange={() =>
                      checkboxClick(index, currentElement.selected)
                    }
                    checked={currentElement.selected}
                  />
                  {currentElement.name}
                </label>
              </li>
            ))}
          </ul>
        </div>
        <div className="model_foot">
          <div className="button_container">
            <Button
              buttonType="default"
              text="Cancel"
              Click={() => props.Click("")}
            />
            <Button
              buttonType="default"
              text="Okay"
              Click={() => props.okayClick(state)}
            />
          </div>
        </div>
      </div>
    </div>
  );
};

export default Model;

Expected Behavior: this.state.List of Class Component should only Update if Okay Button is Pressed.

Current Behavior: this.state.list of Class Component is Updated as soon as Functional Component state is Updated


Solution

  • You are mutating the state here checkboxClick:

    const newState = [...state]; // shallow clone of the state array, objects inside the array are not cloned
    const newElement = newState[index]; // getting reference to the original object
    newElement.selected = !selected; // mutating the original object
    newState[index] = newElement; // replacing the original object reference with the original object reference does nothing
    

    Example:

    const state = [{}];
    
    const newState = [...state];
    const newElement = newState[0];
    newElement.selected = true;
    
    console.log(state[0] === newElement);

    You also need to clone the object you're changing:

    const state = [{}];
    
    const newState = [...state];
    const newElement = { ...newState[0] };
    newElement.selected = true;
    
    console.log(state[0] === newElement);