我在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;
2条答案
按热度按时间bxgwgixi1#
这里的信息量有些有限,但通常这种错误可以很容易地避免,只要
key
不仅保持唯一性和可预测性(因此ReactJS不会将循环组件视为新组件)。你可以让你的密钥更独特一点,就像这样
最后,你们一起,会得到这个
希望这能解决你的问题。
p4rjhz4m2#
通过使所有
hairstyle.name
唯一来修复此问题。因此,我必须确保数据库中的所有发型名称都是唯一的。我不知道为什么我得到了一个错误与key
时,这是问题。我希望我能保留重复的发型名称。