在Nextjs13中遇到两个具有相同密钥的孩子

omjgkv6w  于 2023-10-18  发布在  其他
关注(0)|答案(2)|浏览(121)

我在nextjs 13中用shadcn/ui做了一个预订表单。我在我的postgres数据库中Map发型,让用户选择发型。我得到错误Encountered two children with the same key , 'Coils/Butterfly Locs/Individual Braids/etc.。我使用postgres查询确认我所有的hairstkye id都是唯一的。我不确定这是否与组件的顺序有关。任何帮助都非常感谢,也许一个更好的方法来做到这一点将是有益的。这是某些发型是给这个错误,他们有独特的身份证,所以我很困惑。如果需要的话,我可以发送我的发型表的csv。
我的预订表单组件:

"use client";

import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import {
    Popover,
    PopoverTrigger,
    PopoverContent,
} from "@/components/ui/popover";
import {
    Select,
    SelectContent,
    SelectItem,
    SelectTrigger,
    SelectValue,
} from "@/components/ui/select";
import { format } from "date-fns";
import { Calendar } from "@/components/ui/calendar";
import { Button } from "@/components/ui/button";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Card, CardTitle, CardContent } from "@/components/ui/card";
import {
    Form,
    FormField,
    FormItem,
    FormLabel,
    FormControl,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { CalendarIcon } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import React, { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Textarea } from "@/components/ui/textarea";
import { Hairstyles } from "@prisma/client";

interface BookingFormProps {
    adultHairstyles: Hairstyles[];
    colourServices: Hairstyles[];
    kidsHairstyles: Hairstyles[];
    locsHairTreatmentHairstyles: Hairstyles[];
    locsPackageHairstyles: Hairstyles[];
    locsStylesHairstyles: Hairstyles[];
    naturalHairHairstyles: Hairstyles[];
    silkPressHairstyles: Hairstyles[];
    simpleLocsStylesHairstyles: Hairstyles[];
    starterLocsHairstyles: Hairstyles[];
}

const phoneRegex = new RegExp(
    /^([+]?[\s0-9]+)?(\d{3}|[(]?[0-9]+[)])?([-]?[\s]?[0-9])+$/
);

const formSchema = z.object({
    firstname: z.string().min(2, "Too Short!").max(50, "Too Long!"),
    lastname: z.string().min(2, "Too Short!").max(50, "Too Long!"),
    phone: z.string().regex(phoneRegex, "Invalid Number!"),
    hairIsWashed: z.boolean().default(false).optional(),
    date: z.date().min(new Date(), "Invalid Date!"),
    time: z.string().min(4, "Invalid Time!").max(5, "Invalid Time!"),
    hairstyle: z.string().min(2, "Too Short!").max(50, "Too Long!"),
    additionalInfo: z.string().min(2, "Too Short!").max(50, "Too Long!"),
});

function BookingForm({
    adultHairstyles,
    colourServices,
    kidsHairstyles,
    locsHairTreatmentHairstyles,
    locsPackageHairstyles,
    locsStylesHairstyles,
    naturalHairHairstyles,
    silkPressHairstyles,
    simpleLocsStylesHairstyles,
    starterLocsHairstyles,
}: BookingFormProps) {
    const [loading, setLoading] = useState<boolean>(false);

    const form = useForm<z.infer<typeof formSchema>>({
        resolver: zodResolver(formSchema),
        defaultValues: {
            firstname: "",
            lastname: "",
            phone: "",
            hairIsWashed: false,
            date: new Date(),
            time: "",
            hairstyle: "",
            additionalInfo: "",
        },
    });

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

    return (
        <Form {...form}>
            <form onSubmit={form.handleSubmit(onSubmit)}>
                <Card className="w-full p-10 space-y-8 gap-2 mb-20 bg-slate-100">
                    <CardTitle className="text-center">Book an Appointment</CardTitle>
                    <CardContent>
                        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                            <FormField
                                name="firstname"
                                control={form.control}
                                render={({ field }) => (
                                    <FormItem>
                                        <FormLabel>Firstname</FormLabel>
                                        <FormControl>
                                            <Input {...field} type="text" placeholder="John" />
                                        </FormControl>
                                    </FormItem>
                                )}
                            />
                            <FormField
                                name="lastname"
                                control={form.control}
                                render={({ field }) => (
                                    <FormItem>
                                        <FormLabel>Lastname</FormLabel>
                                        <FormControl>
                                            <Input {...field} type="text" placeholder="Doe" />
                                        </FormControl>
                                    </FormItem>
                                )}
                            />
                            <FormField
                                name="phone"
                                control={form.control}
                                render={({ field }) => (
                                    <FormItem>
                                        <FormLabel>Phone</FormLabel>
                                        <FormControl>
                                            <Input {...field} type="text" placeholder="" />
                                        </FormControl>
                                    </FormItem>
                                )}
                            />
                            <FormField
                                name="date"
                                control={form.control}
                                render={({ field }) => (
                                    <FormItem className="flex flex-col mt-2">
                                        <FormLabel>Date</FormLabel>
                                        <Popover>
                                            <PopoverTrigger asChild>
                                                <Button
                                                    variant="outline"
                                                    className={cn(
                                                        "w-[280px] justify-start text-left font-normal",
                                                        !field.name && "text-muted-foreground"
                                                    )}
                                                >
                                                    <CalendarIcon className="mr-2 h-4 w-4" />
                                                    {field.value ? (
                                                        format(field.value, "PPP")
                                                    ) : (
                                                        <span>Pick a date</span>
                                                    )}
                                                </Button>
                                            </PopoverTrigger>

                                            <PopoverContent>
                                                <FormControl>
                                                    <Calendar
                                                        mode={"single"}
                                                        {...field}
                                                        selected={field.value}
                                                        onSelect={(date) => field.onChange(date)}
                                                    />
                                                </FormControl>
                                            </PopoverContent>
                                        </Popover>
                                    </FormItem>
                                )}
                            />
                            <FormField
                                name="time"
                                control={form.control}
                                render={({ field }) => (
                                    <FormItem>
                                        <FormLabel>Time</FormLabel>
                                        <FormControl>
                                            <Input {...field} type="time" placeholder="" />
                                        </FormControl>
                                    </FormItem>
                                )}
                            />
                            <FormField
                                name="hairstyle"
                                control={form.control}
                                render={({ field }) => (
                                    <FormItem>
                                        <FormLabel>Hairstyle</FormLabel>
                                        <Select
                                            onValueChange={field.onChange}
                                            defaultValue={field.value}
                                        >
                                            <FormControl>
                                                <SelectTrigger className="w-[180px]">
                                                    <SelectValue placeholder="Choose a hairstyle..." />
                                                </SelectTrigger>
                                            </FormControl>
                                            <SelectContent>
                                                <ScrollArea className="h-[200px] w-[350px] rounded-md border p-4">
                                                    <p className="text-base text-gray-400">Adults</p>
                                                    {adultHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">
                                                        Colour Services
                                                    </p>
                                                    {colourServices.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">Kids</p>
                                                    {kidsHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">
                                                        Locs Hair Treatment
                                                    </p>
                                                    {locsHairTreatmentHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">
                                                        Locs Package
                                                    </p>
                                                    {locsPackageHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">Locs Styles</p>
                                                    {locsStylesHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">
                                                        Natural Hair & External Care
                                                    </p>
                                                    {naturalHairHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">Silk Press</p>
                                                    {silkPressHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">
                                                        Simple Locs Styles
                                                    </p>
                                                    {simpleLocsStylesHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                    <p className="text-base text-gray-400">
                                                        Starter Locs
                                                    </p>
                                                    {starterLocsHairstyles.map((hairstyle) => (
                                                        <SelectItem
                                                            key={hairstyle.id}
                                                            value={hairstyle.name}
                                                            className="font-semibold text-sm"
                                                        >
                                                            {hairstyle.name}
                                                        </SelectItem>
                                                    ))}
                                                </ScrollArea>
                                            </SelectContent>
                                        </Select>
                                    </FormItem>
                                )}
                            />
                            <FormField
                                name="hairIsWashed"
                                control={form.control}
                                render={({ field }) => (
                                    <FormItem className="flex flex-col space-y-4">
                                        <FormLabel>
                                            Will your hair be prewashed before appointment?
                                        </FormLabel>
                                        <p className="text-sm text-muted-foreground">
                                            Check the box if so.
                                        </p>
                                        <FormControl>
                                            <Checkbox
                                                checked={field.value}
                                                onCheckedChange={field.onChange}
                                            />
                                        </FormControl>
                                    </FormItem>
                                )}
                            />
                            <FormField
                                name="additionalInfo"
                                control={form.control}
                                render={({ field }) => (
                                    <FormItem className="flex flex-col">
                                        <FormLabel>Additional Info</FormLabel>
                                        <FormControl>
                                            <Textarea
                                                {...field}
                                                placeholder="Any additional info you want to add..."
                                            />
                                        </FormControl>
                                    </FormItem>
                                )}
                            />
                        </div>
                    </CardContent>
                    <div className="flex justify-center items-center">
                        <Button type="submit" className="text-center">
                            {loading ? "Loading..." : "Book an Appointment"}
                        </Button>
                    </div>
                </Card>
            </form>
        </Form>
    );
}

export default BookingForm;

My page.tsx:

import BookingForm from "@/components/BookingForm";
import prisma from "@/lib/db";

async function getAdultHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "adults",
        },
    });

    return hairstyles;
}

async function getColourServicesHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "Colour-Services",
        },
    });

    return hairstyles;
}

async function getKidsHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "kids",
        },
    });

    return hairstyles;
}

async function getLocsHairTreatmentHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "Locs-Hair-Treatment",
        },
    });

    return hairstyles;
}

async function getLocsPackageHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "Locs-Package",
        },
    });

    return hairstyles;
}

async function getLocsStylesHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "Locs-Styles-and-Protective-Styles",
        },
    });

    return hairstyles;
}

async function getNaturalHairHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "Natural-Hair-and-External-Care",
        },
    });

    return hairstyles;
}

async function getSilkPressHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "Silk-Press",
        },
    });

    return hairstyles;
}

async function getSimpleLocsHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "Simple-Locs-Styles",
        },
    });

    return hairstyles;
}

async function getStarterLocsHairstyles() {
    const hairstyles = await prisma.hairstyles.findMany({
        where: {
            category: "Starter-Locs",
        },
    });

    return hairstyles;
}

async function BookingPage() {
    const adultHairstyles = await getAdultHairstyles();
    const colourServices = await getColourServicesHairstyles();
    const kidsHairstyles = await getKidsHairstyles();
    const locsHairTreatmentHairstyles = await getLocsHairTreatmentHairstyles();
    const locsPackageHairstyles = await getLocsPackageHairstyles();
    const locsStylesHairstyles = await getLocsStylesHairstyles();
    const naturalHairHairstyles = await getNaturalHairHairstyles();
    const silkPressHairstyles = await getSilkPressHairstyles();
    const simpleLocsStylesHairstyles = await getSimpleLocsHairstyles();
    const starterLocsHairstyles = await getStarterLocsHairstyles();

    return (
        <BookingForm
            adultHairstyles={adultHairstyles}
            colourServices={colourServices}
            kidsHairstyles={kidsHairstyles}
            locsHairTreatmentHairstyles={locsHairTreatmentHairstyles}
            locsPackageHairstyles={locsPackageHairstyles}
            locsStylesHairstyles={locsStylesHairstyles}
            naturalHairHairstyles={naturalHairHairstyles}
            silkPressHairstyles={silkPressHairstyles}
            simpleLocsStylesHairstyles={simpleLocsStylesHairstyles}
            starterLocsHairstyles={starterLocsHairstyles}
        />
    );
}

export default BookingPage;

bxgwgixi

bxgwgixi1#

这里的信息量有些有限,但通常这种错误可以很容易地避免,只要key不仅保持唯一性和可预测性(因此ReactJS不会将循环组件视为新组件)。

adultHairstyles.map((hairstyle, i) => ( // 2nd argument of map `i` is index number

你可以让你的密钥更独特一点,就像这样

key={`${hairstyle.id}-${i}`}

最后,你们一起,会得到这个

{adultHairstyles.map((hairstyle, i) => (
  <SelectItem
    key={`${hairstyle.id}-${i}`}
    value={hairstyle.name}
    className="font-semibold text-sm"
  >
    {hairstyle.name}
  </SelectItem>
))}

希望这能解决你的问题。

p4rjhz4m

p4rjhz4m2#

通过使所有hairstyle.name唯一来修复此问题。因此,我必须确保数据库中的所有发型名称都是唯一的。我不知道为什么我得到了一个错误与key时,这是问题。我希望我能保留重复的发型名称。

相关问题