Home > Back-end >  How do I make React onClick calls only affect a single item, instead of all?
How do I make React onClick calls only affect a single item, instead of all?

Time:10-31

I have an array of objects, and for each one I .map it into a component called Card.js. Each card has an 'edit' button, and I have an edit form which I want to appear ONLY for the card on which I clicked the button.

At the moment, whatever I try to do to pass an id into the Editform.js component, it still makes the form appear for all of the card components.

Here's the current component I call which is meant to render just form for the clicked button. I pass in all of the cards in the 'cards' array, and what I believe is the id of the current .map object from the calling function:

function Editform({ cards, setCards, id }) {
  const thisCard = cards.filter((card) => card.id === id)[0];
  const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
  console.log(editThisCard);

  return (
    <>
      {editThisCard && ( // should only render if editThisCard is true.
        <div className="form">
          <p>Name of game:</p>
          <input type="text" value={thisCard.gamename}></input>

          <p>Max players: </p>
          <input type="text" value={thisCard.maxplayers}></input>
          <p>Free spaces: </p>
          <input type="text" value={thisCard.freespaces}></input>

          <p>Table #: </p>
          <input type="text" value={thisCard.tablenum}></input>
          <p></p>
          <button type="button" className="playbutton">
            Save changes
          </button>
        </div>
      )}
    </>
  );
}

export default Editform;

edit: apologies, I forgot to paste in the other code. Here it is. Note that I'm just hardcoding in a couple of cards for now:

import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";

function Displaycards({ lastid }) {
  const [cards, setCards] = React.useState([
    {
      id: 1,
      gamename: "El Dorado",
      maxplayers: 4,
      freespaces: 1,
      tablenum: 5,
    },
    {
      id: 2,
      gamename: "Ticket to Ride",
      maxplayers: 4,
      freespaces: 2,
      tablenum: 3,
    },
  ]); // using the React state for the cards array

  const [showForm, setShowForm] = React.useState((false);

  return (
    <div className="cardwrapper">
      {cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
        return (
          <div key={id}>
            <div>
              <div className="card">
                <ReactFitText compressor={0.8}>
                  <div className="gamename">{gamename}</div>
                </ReactFitText>
                <div className="details">
                  <p>Setup for: </p>
                  <p className="bignumbers">{maxplayers}</p>
                </div>
                <div className="details">
                  <p>Spaces free:</p>
                  <p className="bignumbers">{freespaces}</p>
                </div>
                <div className="details">
                  <p>Table #</p>
                  <p className="bignumbers">{tablenum}</p>
                </div>
                <button type="button" className="playbutton">
                  I want to play
                </button>
                <br />
              </div>

              <div className="editbuttons">
                <button
                  type="button"
                  className="editbutton"
                  onClick={() => setShowForm(!showForm)}
                >
                  Edit
                </button>
                <button type="button" className="delbutton">
                  X
                </button>
              </div>
              {showForm && (
                <div>
                  <Editform
                    cards={cards}
                    setCards={setCards}
                    id={id}
                  />
                </div>
              )}
            </div>
          </div>
        );
      })}
    </div>
  );
}

export default Displaycards;

I feel like I'm missing something obvious, but I can't get my head around what it is. The current iteration of it is here - https://github.com/TSDAdam/lfp/tree/usestate-trial - and it was created with create-react-app .

CodePudding user response:

It sounds like you have one state controlling all of the Cards. You haven't shown the Card component yet however. Have every Card control its own state, so when the edit button bound to the card is clicked, it only applies to that one card. If you show us more code we can narrow it down, but this is most likely the gist of your problem.

CodePudding user response:

The problem is that the EditForm is inside the map function, so for every item in your cards array, a separate EditForm is rendered with the corresponding values, and all these EditForms get shown/hidden based on the same boolean in your state.

The solution is to move the EditForm outside the map function, and create a new state object that tracks an "active" card, from where the single EditForm could take its values.

This of course won't work if you want to render the EditForm in a position relative to the "active" card.

[Edit]

Okay, I ended my answer with a caveat, but I should add a solution for that as well, since it isn't very complicated.

If you want to render an EditForm below the selected card, for example, the approach would be to keep it inside the map function as it is now, and change the boolean state variable showForm into one that accepts a string/number (depending on what you use as the identifier for each card). And then use this state variable to determine which form shows at any given time.

const [showForm, setShowForm] = React.useState("");

{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
        return (
          <div key={id}>
            // Rest of the JSX
            <div className="editbuttons">
                <button
                  type="button"
                  className="editbutton"
                  onClick={() => setShowForm(id)}
                >
                  Edit
                </button>
                <button type="button" className="delbutton">
                  X
                </button>
              </div>
              {showForm == id && (
                <div>
                  <Editform
                    cards={cards}
                    setCards={setCards}
                    id={id}
                  />
                </div>
              )}
            </div>
          </div>
        );
      })}
  • Related