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

1.9k views Asked by At

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.

1

There are 1 answers

1
Beast80K On BEST ANSWER

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).