skip to Main Content

I’m making a booking form using shadcn/ui in nextjs13. I’m mapping over hairstyles in my postgres db to make selects for the user to choose a hairstyle. I’m getting the error Encountered two children with the same key , 'Coils/Butterfly Locs/Individual Braids/etc.. I confirmed using a postgres query that all my hairstkye ids are unique. I’m not sure if it has something to do with the order of the components are what. Any help is greatly appreciated and maybe a better way to do this will be helpful. It’s certain hairstyles that are giving this error and they have unique ids so I’m confused. I can send the csv of my hairstyles table if necessary.

My booking form component:

"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;

enter image description here

2

Answers


  1. Chosen as BEST ANSWER

    Fixed the issue by making all hairstyle.name unique. Therefore, I had to make sure all the hairstyles names in the database was unique. I don't know why I got an error with key when that was the issue. I would prefer if I could keep the duplicate hairstyle names.


  2. There’s somewhat limited amount of information here, but generally this kind of error can easily be avoid as long as the key remains not only unique and predictable (so ReactJS doesn’t treat the looped component as new component) by doing so

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

    so you can make your key a bit more unique like so —

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

    Finally, together, you will have this —

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

    Hope this will solve your issue.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search