React.js how to get an element and paint it a different color

I'm making a cinema app, where I will select seats in cinema hall and add tickets to the cart

Function to display cinema hall:

export default function CinemaHall(props) {
   const row = props.row;
   const seats = props.seats;
   const hall = [];

   const [isActive, setIsActive] = useState(false);

   function getSeats(place) {
       setIsActive(isActive => !isActive);
       console.log("Row: " + row, "Place: " + place);
   }

   for(let i = 1; i < seats; i++) {
       hall.push(
          <div className="seats" onClick={() => getSeats(i)} style={{backgroundColor: isActive ? "rgb(255, 208, 0)" : "rgb(58, 130, 218)"}}></div>
       )
   } 

   return hall;
}

It returns my hall (look at image):

enter image description here

But if I click on the seat, then not one seat is repainted, but a whole row. How to fix it?

Upvotes: 1

Views: 231

Answers (2)

Yuri
Yuri

Reputation: 70

The main problem is that you have one state for all places. You can do something like this. And in my opinion you have an unnecessary variable like a hall.

const CinemaHall = ({ seats = [1, 2, 3, 4, 5] }) => {

  const [seatsWithStatus, setSeatsWithStatus] = useState(() => {
    return seats.map((seat, idx) => (
      {
        id: idx, // just in case
        seat,
        isActive: false
      }
    )
    )
  });

  const getSeats = (id) => {
    const foundSeat = seatsWithStatus.find(seat => seat.id === id);
    foundSeat.isActive = !foundSeat.isActive;
    setSeatsWithStatus(seatsWithStatus.map(seat => {
      if (seat.id === id) {
        return foundSeat;
      }
      return seat
    }))
  }

  const showSeatsElement = () => (
    seatsWithStatus.map((seat, idx) => (
      <div className="seats"
        key={idx}
        onClick={() => getSeats(seat.id)}
        style={{ backgroundColor: seat.isActive ? "rgb(255, 208, 0)" : "rgb(58, 130, 218)"}}
      ></div>
    ))
  )

  return (
    <>
      {showSeatsElement()}
    </>
  )
}

export default CinemaHall;

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1074485

There are a few things, but the main thing is that you'll need to maintain a separate "active" flag for each seat. For instance, you might have a Set of active seats.

See comments for details and other notes:

export default function CinemaHall({ row, seats }) {
    // Maintain a map of the seats that are "active"
    const [seatsActive, setSeatsActive] = useState(new Set());

    function toggleSeat(place) {
        // Generally, use the callback form when updating state based on
        // existing state
        setSeatsActive((previous) => {
            // Get a new set
            const update = new Set(previous);
            // Toggle this seat: remove it if it's in the set, add it if
            // it isn't
            if (update.has(place)) {
                update.delete(place);
            } else {
                update.add(place);
            }
            console.log(`Row: ${row} Place: ${place} Active: ${update.has(place)}`);
            return update;
        });
    }

    const hall = [];
    // If the first seat is `1`, you want to loop until `<= seats`, not
    // just `< seats`, if you want `seats` number of seats
    for (let seat = 1; seat <= seats; seat++) {
        // Note the `key` prop -- it's important to include that in arrays
        // managed by React. In this case, we can use the seat number, but
        // beware there are many times you can't use an array index as a key!
        // Note using `seatsActive.has(seat)` to see if the seat is "active"
        hall.push(
            <div
                key={seat}
                className="seats"
                onClick={() => toggleSeat(seat)}
                style={{ backgroundColor: seatsActive.has(seat) ? "rgb(255, 208, 0)" : "rgb(58, 130, 218)" }}
            />
            // Note that in JSX (unlike HTML), you can self-close void
            // element tags like `div`
        );
    }

    return hall;
}

Re my note about using seat as the key: It's fine here, but usually using a loop counter is not okay, details in this article linked from the React keys documentation.


Side note: I'd use a class, not inline styling, to indicate active seats:

<div
    // ...
    className={`seat ${seatsActive.has(seat) ? "active" : ""}`}
/>

Upvotes: 1

Related Questions