typescriptdatatablenext.js13react-tabletanstack

Why can't I get the values of a single row using Tanstack React Table?


I'm working on a project using NextJS 13. I have a Tanstack Data Table and I'm using cell actions to edit or delete a row.

Data Table

When I click on "Modify" button (Blue), It opens a modal to edit the fields corresponding to the row.

Modify Modal.

However, the problem is that no matter which row I select to modify, it always displays the data of the last row in its corresponding inputs.

DataTable columns definition:


import { ColumnDef } from '@tanstack/react-table'
import ExpenseActions from './ExpenseActions';

export type Expense = {
    id: string;
    description: string;
    value: number;
    date_of_expense: Date;
}

export const columnsExpenses: ColumnDef<Expense>[] = [
    {
        accessorKey: 'description',
        header: 'Descripcion',
    },
    {
        accessorKey: 'value',
        header: 'Gasto',
        cell: ({ row }) => {
            const amount = parseFloat(row.getValue('value'))
            const formatted = new Intl.NumberFormat('es-CL', {
                style: 'currency',
                currency: 'CLP'
            }).format(amount);
            return <div className='text-right font-medium'>{formatted}</div>
        }
    },
    {
        accessorKey: 'date_of_expense',
        header: 'Fecha Gasto',
        cell: ({ row }) => {
            const day = row.original.date_of_expense.getDate();
            const month = row.original.date_of_expense.getMonth() + 1;
            const year = row.original.date_of_expense.getFullYear();

            return `${day}/${month}/${year}`;
        }
    },
    {
        id: 'actions',
        header: 'Acciones',
        cell: ({ row }) => (
            <ExpenseActions
                expenseId={row.original.id}
                value={row.original.value}
                description={row.original.description}
                dateOfExpense={row.original.date_of_expense}

            />
        )
    }
]

ExpenseActions.tsx:

'use client'
import { Button } from '@/components/ui/button';
import axios from 'axios';
import { Expense } from '@prisma/client';
import {
    AlertDialog,
    AlertDialogAction,
    AlertDialogCancel,
    AlertDialogContent,
    AlertDialogDescription,
    AlertDialogFooter,
    AlertDialogHeader,
    AlertDialogTitle,
    AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import toast from 'react-hot-toast';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useModal } from '@/hooks/useModal';
import EditExpenseModal from '@/components/modal/EditExpenseModal';
import { Row } from '@tanstack/react-table';
import { Payment } from './columns';

interface ExpenseActionsProps {
    expenseId: string;
    value: number;
    description: string;
    dateOfExpense: Date;
}

const ExpenseActions = ({
    expenseId,
    value,
    description,
    dateOfExpense
}: ExpenseActionsProps) => {

    const router = useRouter();
    const { onOpen, isOpen } = useModal();

    const deleteExpense = async () => {
        const isLoading = toast.loading('Eliminando...');

        await axios.delete(`/api/expense/${expenseId}`)
            .then(() => {
                toast.dismiss(isLoading);
                toast.success('Item eliminado con éxito!');
                router.refresh();
            })
            .catch(() => {
                toast.error('Error al eliminar item. Por favor intentelo denuevo.');
            })
            .finally(() => {
                toast.dismiss(isLoading);
            })

    }

    return (
        <>

            <EditExpenseModal
                expenseId={expenseId}
                value={value}
                description={description}
                dateOfExpense={dateOfExpense}
            />

            <div className='flex justify-center gap-x-2'>
                <AlertDialog>
                    <AlertDialogTrigger className='bg-red-700 rounded-lg p-2 text-sm'>Eliminar</AlertDialogTrigger>
                    <AlertDialogContent>
                        <AlertDialogHeader>
                            <AlertDialogTitle>¿Está seguro que quiere eliminar el siguiente gasto?: {value}.</AlertDialogTitle>
                            <AlertDialogDescription>
                                Está acción no se puede deshacer.
                            </AlertDialogDescription>
                        </AlertDialogHeader>
                        <AlertDialogFooter>
                            <AlertDialogCancel>Cancel</AlertDialogCancel>
                            <Button onClick={deleteExpense} variant='destructive'>Eliminar</Button>
                        </AlertDialogFooter>
                    </AlertDialogContent>
                </AlertDialog>
                <Button id='edit-expense' onClick={() => { onOpen('editExpense') }} size='sm' variant='primary'>
                    Modificar
                </Button>

            </div>
        </>
    );
}

export default ExpenseActions;

EditExpenseModal.tsx:

import * as z from 'zod';
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { cn } from '@/lib/utils';
import axios from 'axios';
import { useForm } from 'react-hook-form';
import { format } from 'date-fns';
import { useModal } from '@/hooks/useModal';
import { CircleDashed, DollarSign, CalendarIcon } from 'lucide-react';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '../ui/form';
import { Calendar } from '../ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import { Label } from '../ui/label';
import { Input } from '../ui/input';
import { Button } from '../ui/button';

interface EditExpenseModalProps {
    expenseId:string;
    value: number;
    description: string;
    dateOfExpense: Date;
}

const formSchema = z.object({
    value: z.coerce
        .number({
            required_error: 'Valor Requerido.',
        })
        .int({
            message: 'El valor debe ser un numero.'
        }),
    description: z.string().min(5, { message: 'La descripcion debe tener al menos 5 carácteres' }),
    dateOfExpense: z.date({
        required_error: 'Se requiere una fecha del gasto que realizó'
    })
})

const EditExpenseModal = ({
    expenseId,
    value,
    description,
    dateOfExpense
}: EditExpenseModalProps) => {

    const [isMounted, setIsMounted] = useState(false);
    const { isOpen, onClose, type, onOpen } = useModal();

    const isModalOpen = isOpen && type === 'editExpense';

    const form = useForm<z.infer<typeof formSchema>>({
        resolver: zodResolver(formSchema),
        defaultValues: {
            value: value,
            description: description,
            dateOfExpense:dateOfExpense
        },
    })

    const isSubmitting = form.formState.isSubmitting;


    const onSubmit = async (values: z.infer<typeof formSchema>) => {

        await axios.patch(`/api/expense/${expenseId}`, values);

        form.reset();
        onClose();
        window.location.reload();
    }

    useEffect(() => {
        setIsMounted(true);
    }, []);

    if (!isMounted) {
        return null;
    }

    return (
        <Dialog open={isModalOpen} onOpenChange={onClose}>
            <DialogContent className='bg-white text-black p-0 overflow-hidden dark:bg-[#313338]'>
                <DialogHeader className='pt-8 px-6'>
                    <DialogTitle className='text-2xl text-center font-bold text-black dark:text-white'>
                        Modificar Gasto.
                    </DialogTitle>
                </DialogHeader>
                <div className='p-6'>
                    <Form {...form}>
                        <form onSubmit={form.handleSubmit(onSubmit)} className='text-black dark:text-white'>
                            <FormField
                                control={form.control}
                                name='value'
                                render={({ field }) => (
                                    <FormItem>
                                        <FormLabel className='text-lg'>Valor Gastado.</FormLabel>
                                        <div className='flex flex-row items-center'>
                                            <DollarSign size={20} className='absolute' />
                                            <FormControl>
                                                <Input
                                                    type='number'
                                                    placeholder='Ingrese la cantidad que gastó'
                                                    defaultValue={value}
                                                    className='pl-6 pb-2'
                                                    disabled={isSubmitting}
                                                    {...field}
                                                />
                                            </FormControl>
                                        </div>
                                        <FormMessage className='text-red-600' />
                                    </FormItem>
                                )}
                            />
                            <FormField
                                control={form.control}
                                name='description'
                                render={({ field }) => (
                                    <FormItem className='pt-5'>
                                        <FormLabel className='text-lg'>Descripcion del Gasto.</FormLabel>
                                        <FormControl>
                                            <Input
                                                placeholder='Indique pequeña descripcion de lo que gastó'
                                                defaultValue={description}
                                                disabled={isSubmitting}
                                                {...field}
                                            />
                                        </FormControl>
                                        <FormDescription>
                                            Ingrese el nombre del producto o una pequeña descripcion de lo que gastó.
                                        </FormDescription>
                                        <FormMessage className='text-red-600' />
                                    </FormItem>
                                )}
                            />
                            <FormField
                                control={form.control}
                                name="dateOfExpense"
                                render={({ field }) => (
                                    <FormItem className="flex flex-col mt-5">
                                        <FormLabel className='text-lg'>Fecha del Gasto.</FormLabel>
                                        <Popover>
                                            <PopoverTrigger asChild>
                                                <FormControl>
                                                    <Button
                                                        id='calendar'
                                                        variant="outline"
                                                        className={cn(
                                                            "w-[240px] pl-3 text-left font-normal",
                                                            !field.value && "text-muted-foreground"
                                                        )}
                                                    >
                                                        {field.value ? (
                                                            format(field.value, "PPP")
                                                        ) : (
                                                            <span>Seleccione una fecha.</span>
                                                        )}
                                                        <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
                                                    </Button>
                                                </FormControl>
                                            </PopoverTrigger>
                                            <PopoverContent className="w-auto p-0" align="start">
                                                <Calendar
                                                    mode="single"
                                                    selected={dateOfExpense}
                                                    onSelect={field.onChange}
                                                    disabled={(date) =>
                                                        date > new Date() || date < new Date("1900-01-01")
                                                    }
                                                    initialFocus
                                                />
                                            </PopoverContent>
                                        </Popover>
                                        <FormDescription>
                                            Seleccione la fecha en la que realizó el gasto.
                                        </FormDescription>
                                        <FormMessage className='text-red-600' />
                                    </FormItem>
                                )}
                            />
                            <DialogFooter className='px-6 py-4'>
                                {
                                    isSubmitting ? (
                                        <>
                                            <Button variant='primary' disabled>
                                                <CircleDashed size={20} className='mx-2 animate-spin' />
                                                Modificando...
                                            </Button>
                                        </>
                                    ) : (
                                        <Button variant='primary' type='submit'>
                                            Modificar
                                        </Button>
                                    )
                                }
                            </DialogFooter>
                        </form>
                    </Form>
                </div>
            </DialogContent>
        </Dialog>
    );
}

export default EditExpenseModal;

The modal component only gets the last values of the Table. However, when I click on the "Delete" button (Red), the printed value gets the correct value of the row. Delete Modal Delete Modal

What I'm missing here?.

Thank you very much in advance.


Solution

  • Problem :

    However, the problem is that no matter which row I select to modify, it always displays the data of the last row in its corresponding inputs. The modal component only gets the last values of the Table.

    Possible Cause :

    Not passing other values,

    Solution :

    Check this Line In your ExpenseActions.tsx file :

     <AlertDialogTitle>¿Está seguro que quiere eliminar el siguiente gasto?: {value}.</AlertDialogTitle>
    

    If you look at the code closely, you haven't used other props {expenseId,description,dateOfExpense} in <AlertDialog> just used {value}


    Regarding :

    However, when I click on the "Delete" button (Red), the printed value gets the correct value of the row.

    Explaination : Look in DataTable columns definition you have passed,

    {
            id: 'actions',
            header: 'Acciones',
            cell: ({ row }) => (
                <ExpenseActions
                    expenseId={row.original.id}
                    value={row.original.value}
                    description={row.original.description}
                    dateOfExpense={row.original.date_of_expense}
    
                />
            )
        }
    

    In your ExpenseActions.tsx has props

    {
        expenseId,
        value,
        description,
        dateOfExpense
    }
    

    deleteExpense function is using that prop

    await axios.delete(`/api/expense/${expenseId}`)
    

    If you still have doubts or if I didn't solved your problem then Please leave a comment (i will update the answer).