Search code examples
reactjsreduxreact-hooksmaterial-ui

How to useRef dynamically in tables?


I'm creating a cart page for a booking site in React using Redux and MUI and I have a table in which the user can remove an item from the cart. I wanted to add a smooth animation for the tableRow deletion, but the useRef hook only works on the first tableRow selected. How can I dynamically select the right tableRow? Here's the full .tsx file for the cart page

import { Typography, TableContainer, Table, TableBody, TableHead, TableRow, TableCell, Paper, Stack, Grid, Button, IconButton } from "@mui/material";
import ShoppingCartCheckoutIcon from '@mui/icons-material/ShoppingCartCheckout';
import RemoveShoppingCartIcon from '@mui/icons-material/RemoveShoppingCart';
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { NavBar } from "../components/navbar";
import { useSelector, useDispatch } from "react-redux";
import type { RootState } from "../redux/store";
import { clearCart, removeFromCart } from "../redux/slices/cartSlice";
import { useRef } from "react";

const customTheme = createTheme({
    typography: {
        fontFamily: [
            'Satisfy',
            'cursive'
        ].join(','),
    }
})

export const CartPage = () => {

    const tableRowRef = useRef<HTMLTableRowElement>(null);

    const dispatch = useDispatch();
    const cartItems = useSelector((state: RootState) => state.cart.cart);

    const handleDeleteItem = (id: number) => {
        const tableRow = tableRowRef.current;
        if (tableRow) {
            tableRow.style.animation = 'removeTableRow 1s';
            setTimeout(() => {
                tableRow.style.animation = '';
                dispatch(removeFromCart(id));
            }, 1000);
        }
    }

    return (
        <div>
            <NavBar/>
            <Stack direction='row' sx={{flexWrap: 'wrap'}} className='cartPage'>
                <TableContainer className="scrollableTable" component={Paper} sx={{ width:'50vw', height: '92vh'}}>
                    <Table stickyHeader>
                        <TableHead>
                            <TableRow>
                                <TableCell sx={{ fontSize: '25px' }}><b>Where?</b></TableCell>
                                <TableCell sx={{ fontSize: '25px' }}><b>When?</b></TableCell>
                                <TableCell sx={{ fontSize: '25px' }}><b>Room</b></TableCell>
                                <TableCell sx={{ fontSize: '25px' }}><b>Price</b></TableCell>
                                <TableCell></TableCell>
                            </TableRow>
                        </TableHead>
                        <TableBody>
                            {cartItems.map((item) => (
                                <TableRow key={item.id} ref={tableRowRef}>
                                    <TableCell sx={{ fontSize: '20px' }}>{item.name}</TableCell>
                                    <TableCell sx={{ fontSize: '20px' }}>{item.dates}</TableCell>
                                    <TableCell sx={{ fontSize: '20px' }}><i>{item.room}</i></TableCell>
                                    <TableCell sx={{ fontSize: '20px' }}>${item.price}</TableCell>
                                    <TableCell>
                                        <IconButton 
                                            color='error'
                                            onClick={() => {handleDeleteItem(item.id)}}
                                            >
                                                <ShoppingCartCheckoutIcon fontSize="large" />
                                            </IconButton>
                                        </TableCell>
                             </TableRow>
                         ))}
                     </TableBody>
                    </Table>
                </TableContainer>
                <Grid container justifyContent='center' sx={{width: '40vw'}}>
                    <Stack direction='column' justifyContent='space-evenly'>
                        <ThemeProvider theme={customTheme}> 
                            <Typography variant="h1" className="cartPageCheckoutTypography" sx={{ color: '#74BF8B', textAlign: 'center'}}>
                                Satisfied?
                            </Typography>
                        </ThemeProvider>
                        <Stack direction='row' spacing={1.5}>
                            <Button 
                            variant="contained" 
                            color='success' 
                            size="large" 
                            sx={{ backgroundColor: '#5DBF9A', color: 'white', fontSize: '20px', width: '13vw' }}>
                                Confirm purchase
                            </Button>
                            <Button 
                            variant="contained" 
                            color='success' 
                            size="large" 
                            sx={{ backgroundColor: '#91BE77', color: 'white', fontSize: '20px', width: '13vw' }}
                            href='/book'>
                                Continue booking
                            </Button>
                            <Button 
                            variant="contained" 
                            color='success' 
                            size="large" 
                            sx={{ backgroundColor: '#B9BD5C', color: 'white', fontSize: '20px', width: '13vw' }}
                            onClick={()=>{dispatch(clearCart())}}
                            endIcon={<RemoveShoppingCartIcon />}
                            >
                                Clear cart
                            </Button>
                        </Stack>
                    </Stack>
                </Grid>
            </Stack>
        </div>
    );
};

I tried with

const handleDeleteItem = (id: number) => {
        const tableRow = tableRowRef.current;
        if (tableRow) {
            tableRow.style.animation = 'removeTableRow 1s';
            setTimeout(() => {
                tableRow.style.animation = '';
                dispatch(removeFromCart(id));
            }, 1000);
        }
    }

but it works for the first row only.

Can somebody help me?


Solution

  • Update your ref to use an array instead of null:

    const tableRowRef = useRef<HTMLTableRowElement[]>([]); // I'm not sure about the type in typescript for this
    

    Add the second parameter i.e. index in your cartItems.map() method:

    cartItems.map((item, idx)
    

    Then update the tag:

    <TableRow key={item.id} ref={(el) => (tableRowRef .current[idx] = el)}>
    

    Also update your handleDeleteItem method:

    const handleDeleteItem = (id: number, idx: number) => {
    const tableRow = tableRowRef.current[idx];
      if (tableRow) {
        tableRow.style.animation = "removeTableRow 1s";
        setTimeout(() => {
          tableRow.style.animation = "";
          dispatch(removeFromCart(id));
        }, 1000);
      }
    };