简体   繁体   中英

Can anyone explain why this works with a class component but not a functional component?

Basically, I have several buttons. I want the user to be able to have multiple buttons selected.

I tried using a functional component and storing the button state as an object with the useState hook. The state updates accordingly when the buttons are clicked, but the button's props do not update. I've tried useEffect to rerender the component when props.isActive changes but that did not work.

Using a class component, this works exactly as intended. I'm just trying to understand why that is the case. If anyone can provide insight, I would greatly appreciate it. Thanks.

Functional Component

const View = (props) => {
  var [buttons, setButtons] = useState([
    { name: "Small", isActive: false },
    { name: "Large", isActive: false },
  ]);
  const handleClick = (index) => {
    let tmp = buttons;
    tmp[index].isActive = !tmp[index].isActive;
    return setButtons(tmp);
  };
  return (
      <div>
            {buttons.map((e, index) => {
              return (
                <MyButtonComponent
                  key={index}
                  name={e.name}
                  isActive={e.isActive}
                  onClick={() => handleClick(index)}
                />
              );
            })}
    </div>
  );
};

Class Component

class View extends Component {
  state = {
    btn: [
      { name: "Small", isActive: false },
      { name: "Large", isActive: false },
    ],
  };
  handleClick = (index) => {
    let tmp = this.state.btn;
    tmp[index].isActive = !tmp[index].isActive;
    return this.setState({ ...this.state, btn: tmp });
  };
  render() {
    return (
            <div>
              {this.state.btn.map((e, index) => {
                return (
                  <MyButtonComponent
                    key={index}
                    name={e.name}
                    isActive={e.isActive}
                    onClick={() => this.handleClick(index)}
                  />
                );
              })}
            </div>
    );
  }
}

You are mutating your old array, and then setting state with the mutated array. This is not a good idea in react, regardless of whether you're using class components or function components. The class component let you get away with it, but the function component compares the state before with the sate after and sees that they're the same array, so it skips rendering.

To fix this, you should create a new state instead of mutating the old. Change this:

let tmp = buttons;
tmp[index].isActive = !tmp[index].isActive;
return setButtons(tmp);

To this:

// Create a copy of the array
let tmp = [...buttons]; 
// Also copy the item you want to change
tmp[index] = {
  ...tmp[index],
  active: !tmp[index].active
}
setState(tmp);

You're updating the reference and setting the same reference to the state (setButtons(tmp)) , which react thinks , array didn't change because of shallow comparison. You need use new reference. Like the following

    let tmp = buttons; <-- problem is here, reference 
    tmp[index].isActive = !tmp[index].isActive;
    return setButtons(tmp); <-- and updating same `reference`
   const handleClick = (index) => {
     buttons[index].isActive = !buttons[index].isActive;
     return setButtons([...buttons]); <-- this will work
   };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM