Chett
Chett

Reputation: 91

React Button Click Event Update State Value But Not Reflected on Button Text

Still learning some ropes of React.

I have the following code where I render a list of buttons:

import React, { useState, useEffect } from 'react';
const MyComponent = (props) => {
  const [buttonList, setButtonList] = useState([]);
  useEffect(() => { getButtonList()}, [])

  const getButtonList = () => {
    let data = [
      {id: 1, name: 'One', selected: false },
      {id: 2, name: 'Two', selected: false },
      {id: 3, name: 'Three', selected: false }
    ]

    setButtonList(data)
  }

  const ButtonItem = ({ item }) => {
    const btnClick = (event) => {
      const id = event.target.value

      buttonList.forEach((el) => {
        el.isSelected = (el.id == id) ? true : false
      })

      setButtonList(buttonList)
      console.log('buttonList', buttonList)
    }

    return (
      <button type="button" 
       className={ "btn mx-2 " + (item.isSelected ? 'btn-primary' : 'btn-outline-primary') }
       onClick={btnClick} value={item.id}>
       {item.name + ' ' + item.isSelected}
      </button>
    )
  }

  return (
    <div className="container-fluid">
      <div className="card mb-3 rounded-lg">
        <div className="card-body">
          {
            buttonList.map(item => (
              <ButtonItem key={item.id} item={item} />
             ))
           }
         </div>
      </div>
    </div>
  )
}

export default MyComponent;

So the button renders:

[ One false ] [ Two false ] [ Three false ]

And when I click on any Button, I can see on Chrome React Tools that the value for isSelected of that button becomes true. I can also confirm that the specific array item for the button clicked in the dev tools for State (under hooks), the value is true.

The text for the button clicked does not show [ One true ] say if I clicked on button One. What am I missing here?

P.S. Note that I also want to change the class of the button, but I think that part will be resolved if I get the button isSelected value to be known across the component.

Code Demo: https://codesandbox.io/s/laughing-keller-o5mds?file=/src/App.js:666-735

Upvotes: 2

Views: 1311

Answers (2)

Drew Reese
Drew Reese

Reputation: 202608

Issue: You are mutating the state object instead of returning a new state reference. You were also previously using === to compare a string id to a numerical id which was returning false for all comparisons.

Solution: Use a functional update and array.map to update state by returning a new array.

const ButtonItem = ({ item }) => {
  const btnClick = event => {
    const id = event.target.value;

    setButtonList(buttons =>
      buttons.map(button => ({
        ...button,
        isSelected: button.id == id
      }))
    );
  };

  ...
};

Edit elastic-ardinghelli-3dw61

Suggestion: Factor out the btnClick handler, it only needs to be defined once. Curry the id property of item so you can use ===.

const btnClick = id => event => {
  setButtonList(buttons =>
    buttons.map(button => ({
      ...button,
      isSelected: button.id === id
    }))
  );
};

Update the attaching of click handler to pass the item id

const ButtonItem = ({ item }) => {
  return (
    <button
      type="button"
      className={
        "btn mx-2 " +
        (item.isSelected ? "btn-primary" : "btn-outline-primary")
      }
      onClick={btnClick(item.id)} // <-- pass item.id to handler
      value={item.id}
    >
      {item.name + " " + item.isSelected}
    </button>
  );
};

Upvotes: 3

thedude
thedude

Reputation: 9814

In your btnClick handler you are mutating your state, you should create a new value and assign it instead:

import React from "react";
import "./styles.css";
import { useState, useEffect } from "react";

const ButtonItem = ({ item, onClick }) => {
  return (
    <button
      type="button"
      className={
        "btn mx-2 " + (item.isSelected ? "btn-primary" : "btn-outline-primary")
      }
      onClick={() => onClick(item.id)}
      value={item.id}
    >
      {item.name + " " + item.isSelected}
    </button>
  );
};

const MyComponent = props => {
  const [buttonList, setButtonList] = useState([]);
  useEffect(() => {
    getButtonList();
  }, []);

  const getButtonList = () => {
    let data = [
      { id: 1, name: "One", isSelected: false },
      { id: 2, name: "Two", isSelected: false },
      { id: 3, name: "Three", isSelected: false }
    ];

    setButtonList(data);
  };

  const btnClick = id => {
    const updatedList = buttonList.map(el => ({
      ...el,
      isSelected: el.id === id
    }));

    setButtonList(updatedList);
    console.log("buttonList", updatedList);
  };

  return (
    <div className="container-fluid">
      <div className="card mb-3 rounded-lg">
        <div className="card-body">
          {buttonList.map(item => (
            <ButtonItem key={item.id} item={item} onClick={btnClick} />
          ))}
        </div>
      </div>
    </div>
  );
};

export default MyComponent;

Upvotes: 0

Related Questions