0

I am making API calls and rendering different components within an object. One of those is illustrated below:

class Bases extends Component {
    constructor() {
        super();

        this.state = {
            'basesObject': {}
        }
    }

    componentDidMount() {
        this.getBases();
    }

    getBases() {
        fetch('http://localhost:4000/cupcakes/bases')
        .then(results => results.json())
        .then(results => this.setState({'basesObject': results}))
    }

    render() {

        let {basesObject} = this.state;  
        let {bases} = basesObject; 

        console.log(bases); 
        //FALSY values: undefined, null, NaN, 0, false, ""

        return (

            <div>
                {bases && bases.map(item =>
                    <button key={item.key} className="boxes">
                        {/* <p>{item.key}</p> */}
                        <p>{item.name}</p>
                        <p>${item.price}.00</p>
                        {/* <p>{item.ingredients}</p> */}
                    </button>
            )}
           </div>
        )
    }
}

The above renders a set of buttons. All my components look basically the same.

I render my components here:

class App extends Component {
  state = {
    ordersArray: []
  }

  render() {
    return (
      <div>
        <h1>Bases</h1>
        <Bases />
        <h1>Frostings</h1>
        <Frostings />
        <h1>Toppings</h1>
        <Toppings />
      </div>
    );
  }
}

I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.

2 Answers 2

1

enter image description here

Try this

We can use the same component for all categories. All the data is handled by the parent (stateless component).

function Buttons({ list, handleClick }) {
  return (
    <div>
      {list.map(({ key, name, price, isSelected }) => (
        <button
          className={isSelected ? "active" : ""}
          key={key}
          onClick={() => handleClick(key)}
        >
          <span>{name}</span>
          <span>${price}</span>
        </button>
      ))}
    </div>
  );
}

Fetch data in App component, pass the data and handleClick method into Buttons.

class App extends Component {
  state = {
    basesArray: [],
    toppingsArray: []
  };

  componentDidMount() {
    // Get bases and toppings list, and add isSelected attribute with default value false
    this.setState({
      basesArray: [
        { key: "bases1", name: "bases1", price: 1, isSelected: false },
        { key: "bases2", name: "bases2", price: 2, isSelected: false },
        { key: "bases3", name: "bases3", price: 3, isSelected: false }
      ],
      toppingsArray: [
        { key: "topping1", name: "topping1", price: 1, isSelected: false },
        { key: "topping2", name: "topping2", price: 2, isSelected: false },
        { key: "topping3", name: "topping3", price: 3, isSelected: false }
      ]
    });
  }

  // for single selected category
  handleSingleSelected = type => key => {
    this.setState(state => ({
      [type]: state[type].map(item => ({
        ...item,
        isSelected: item.key === key
      }))
    }));
  };

  // for multiple selected category
  handleMultiSelected = type => key => {
    this.setState(state => ({
      [type]: state[type].map(item => {
        if (item.key === key) {
          return {
            ...item,
            isSelected: !item.isSelected
          };
        }
        return item;
      })
    }));
  };

  // get final selected item
  handleSubmit = () => {
    const { basesArray, toppingsArray } = this.state;
    const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
    const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);

    // submit the result here
  }

  render() {
    const { basesArray, toppingsArray } = this.state;

    return (
      <div>
        <h1>Bases</h1>
        <Buttons
          list={basesArray}
          handleClick={this.handleSingleSelected("basesArray")}
        />
        <h1>Toppings</h1>
        <Buttons
          list={toppingsArray}
          handleClick={this.handleMultiSelected("toppingsArray")}
        />
      </div>
    );
  }
}

export default App;

CSS

button {
  margin: 5px;
}
button.active {
  background: lightblue;
}
Sign up to request clarification or add additional context in comments.

6 Comments

This almost works for me but I cant get the buttons to stay clicked like yours. To confirm, did you place the Buttons function at the top of the App.js file?
Also this doesn't add the items into a new array. Im mostly confused on that part.
@CoryAllen did you place the Buttons function at the top of the App.js file -> Yes.
this doesn't add the items into a new array -> it will update the isSelected attribute at the original list, so just filter the list to get the selected items. I add this part into my answer for reference.
can you post the css style you used too?
|
0

I think the following example would be a good start for your case.

Define a handleClick function where you can set state with setState as the following:

handleClick(item) {
  this.setState(prevState => {
    return {
       ...prevState,
       clickedItems: [...prevState.clickedItems, item.key]
    };
  });
}

Create an array called clickedItems in constructor for state and bind handleClick:

constructor() {
   super();
   this.state = {
      basesObject: {},
      clickedItems: [],
   }

   this.handleClick = this.handleClick.bind(this);
}

You need to add a onClick={() => handleClick(item)} handler for onClick:

<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
   {/* <p>{item.key}</p> */}
   <p>{item.name}</p>
   <p>${item.price}.00</p>
   {/* <p>{item.ingredients}</p> */}
</button>

I hope that helps!

5 Comments

This looks great, but what if I dont want to repeat myself? Is there any way I could do the handleClick function inside the App Component and call it on all my other components?
You have a bug: If you write {...prevState, newClickedItems} it will rename clickedItems to newClickedItems Instead write: { ...prevState, clickedItems: [...prevState.clickedItems, item.key] }
@CoryAllen I guess then you need to create the state array in App level and pass down the click event through props to Bases component. So technically you are pushing from child component into parent component's array. I hope that clarifies.
I'm not sure I understand what that would look like.
It's not the bases I need added. I need 1 base, 1 frosting, and unlimited toppings. Those are the elements that need to go into the array.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.