Reputation: 13
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):
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
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
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