admin管理员组

文章数量:1296351

I am trying to build a simple form within a CSR component with Conform and Zod, but when the initial data loads from the server action, the name is populated correctly but the select isn't. The select option "Seleccionar categoría" is the selected option.

This is my component:

"use client"

import { useActionState, useEffect, useState } from "react"
import Form from "next/form"
import { useParams } from "next/navigation"
import { Categoria } from "@prisma/client"
import { getCategory, getCategories, updateCategory } from "@/actions/categorias"
import { useForm } from "@conform-to/react"
import { parseWithZod } from "@conform-to/zod"
import { updateCategorySchema } from "@/lib/schemas"

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"

export default function EditCategoryPage() {
    const { categoryId } = useParams()
    const [category, setCategory] = useState<Categoria | null>(null)
    const [categories, setCategories] = useState<Categoria[]>([])
    const [lastResult, action] = useActionState(updateCategory, undefined)

    useEffect(() => {
        async function fetchData() {
            if (categoryId) {
                const catId = Number(categoryId)
                if (!isNaN(catId)) {
                    const catData = await getCategory(catId)
                    setCategory(catData)
                }
            }
            const cats = await getCategories()
            setCategories(cats)
        }
        fetchData()
    }, [categoryId])

    const [form, fields] = useForm({
        lastResult,
        defaultValue: {
            id: category?.id,
            nombre: category?.nombre,
            // Convertir idPadre a string si existe, de lo contrario, usar cadena vacía.
            idPadre: category?.idPadre ? category.idPadre.toString() : "",
        },
        onValidate({ formData }) {
            return parseWithZod(formData, {
                schema: updateCategorySchema,
            })
        },
        shouldValidate: "onBlur",
        shouldRevalidate: "onInput",
    })

    if (!category) return <p>Cargando...</p>

    return (
        <div className="min-h-screen flex items-center justify-center bg-gray-100 p-4">
            <Card className="w-full max-w-lg">
                <CardHeader>
                    <CardTitle className="text-2xl font-bold text-center">
                        Editar Categoría
                    </CardTitle>
                </CardHeader>
                <CardContent>
                    <Form
                        className="space-y-6"
                        id={form.id}
                        action={action}
                        onSubmit={form.onSubmit}
                    >
                        <p className="text-red-600">{form.errors}</p>
                        {/* Campo oculto para enviar el id */}
                        <input type="hidden" name="id" value={category.id} />

                        {/* Field: Nombre */}
                        <div className="space-y-1">
                            <label
                                htmlFor="nombre"
                                className="block text-sm font-medium text-gray-700"
                            >
                                Nombre
                            </label>
                            <Input
                                className="mt-1"
                                key={fields.nombre.key}
                                name={fields.nombre.name}
                                defaultValue={category.nombre}
                                id="nombre"
                                placeholder="Ingrese el nombre"
                            />
                            {fields.nombre.errors && (
                                <p className="text-xs text-red-500">{fields.nombre.errors}</p>
                            )}
                        </div>

                        {/* Field: Categoría Padre */}
                        <div className="space-y-1">
                            <label
                                htmlFor="idPadre"
                                className="block text-sm font-medium text-gray-700"
                            >
                                Categoría Padre
                            </label>
                            <select
                                className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
                                key={fields.idPadre.key}
                                name={fields.idPadre.name}
                                // Convertimos a string para que coincida con el value de las opciones
                                defaultValue={category?.idPadre ? category.idPadre.toString() : ""}
                                id="idPadre"
                                disabled={categories.length <= 1}
                            >
                                <option value="">
                                    {categories.length > 1 ? "Seleccionar categoría" : "No hay categorías"}
                                </option>
                                {categories
                                    .filter(cat => cat.id !== category.id)
                                    .map((cat) => (
                                        <option key={cat.id} value={cat.id.toString()}>
                                            {cat.nombre}
                                        </option>
                                    ))}
                            </select>
                            {fields.idPadre.errors && (
                                <p className="text-xs text-red-500">{fields.idPadre.errors}</p>
                            )}
                        </div>

                        {/* Botón de submit */}
                        <Button type="submit" className="w-full">
                            Guardar cambios
                        </Button>
                    </Form>
                </CardContent>
            </Card>
        </div>
    )
}

I have added a console.log(category) to log whether the category is loaded and it does, also the category.idPadre has data.

I tried adding a state parentCategory in order to handle the select value and event change but I guess it's not a good practice, since probably I am doing something wrong.

I also tried to use ChatGPT but it may overcomplicated things (not working). Converting category.idPadre into a string was an approach the LLM decided to use, but it didn't work either.

Any suggestions?

I am trying to build a simple form within a CSR component with Conform and Zod, but when the initial data loads from the server action, the name is populated correctly but the select isn't. The select option "Seleccionar categoría" is the selected option.

This is my component:

"use client"

import { useActionState, useEffect, useState } from "react"
import Form from "next/form"
import { useParams } from "next/navigation"
import { Categoria } from "@prisma/client"
import { getCategory, getCategories, updateCategory } from "@/actions/categorias"
import { useForm } from "@conform-to/react"
import { parseWithZod } from "@conform-to/zod"
import { updateCategorySchema } from "@/lib/schemas"

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"

export default function EditCategoryPage() {
    const { categoryId } = useParams()
    const [category, setCategory] = useState<Categoria | null>(null)
    const [categories, setCategories] = useState<Categoria[]>([])
    const [lastResult, action] = useActionState(updateCategory, undefined)

    useEffect(() => {
        async function fetchData() {
            if (categoryId) {
                const catId = Number(categoryId)
                if (!isNaN(catId)) {
                    const catData = await getCategory(catId)
                    setCategory(catData)
                }
            }
            const cats = await getCategories()
            setCategories(cats)
        }
        fetchData()
    }, [categoryId])

    const [form, fields] = useForm({
        lastResult,
        defaultValue: {
            id: category?.id,
            nombre: category?.nombre,
            // Convertir idPadre a string si existe, de lo contrario, usar cadena vacía.
            idPadre: category?.idPadre ? category.idPadre.toString() : "",
        },
        onValidate({ formData }) {
            return parseWithZod(formData, {
                schema: updateCategorySchema,
            })
        },
        shouldValidate: "onBlur",
        shouldRevalidate: "onInput",
    })

    if (!category) return <p>Cargando...</p>

    return (
        <div className="min-h-screen flex items-center justify-center bg-gray-100 p-4">
            <Card className="w-full max-w-lg">
                <CardHeader>
                    <CardTitle className="text-2xl font-bold text-center">
                        Editar Categoría
                    </CardTitle>
                </CardHeader>
                <CardContent>
                    <Form
                        className="space-y-6"
                        id={form.id}
                        action={action}
                        onSubmit={form.onSubmit}
                    >
                        <p className="text-red-600">{form.errors}</p>
                        {/* Campo oculto para enviar el id */}
                        <input type="hidden" name="id" value={category.id} />

                        {/* Field: Nombre */}
                        <div className="space-y-1">
                            <label
                                htmlFor="nombre"
                                className="block text-sm font-medium text-gray-700"
                            >
                                Nombre
                            </label>
                            <Input
                                className="mt-1"
                                key={fields.nombre.key}
                                name={fields.nombre.name}
                                defaultValue={category.nombre}
                                id="nombre"
                                placeholder="Ingrese el nombre"
                            />
                            {fields.nombre.errors && (
                                <p className="text-xs text-red-500">{fields.nombre.errors}</p>
                            )}
                        </div>

                        {/* Field: Categoría Padre */}
                        <div className="space-y-1">
                            <label
                                htmlFor="idPadre"
                                className="block text-sm font-medium text-gray-700"
                            >
                                Categoría Padre
                            </label>
                            <select
                                className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
                                key={fields.idPadre.key}
                                name={fields.idPadre.name}
                                // Convertimos a string para que coincida con el value de las opciones
                                defaultValue={category?.idPadre ? category.idPadre.toString() : ""}
                                id="idPadre"
                                disabled={categories.length <= 1}
                            >
                                <option value="">
                                    {categories.length > 1 ? "Seleccionar categoría" : "No hay categorías"}
                                </option>
                                {categories
                                    .filter(cat => cat.id !== category.id)
                                    .map((cat) => (
                                        <option key={cat.id} value={cat.id.toString()}>
                                            {cat.nombre}
                                        </option>
                                    ))}
                            </select>
                            {fields.idPadre.errors && (
                                <p className="text-xs text-red-500">{fields.idPadre.errors}</p>
                            )}
                        </div>

                        {/* Botón de submit */}
                        <Button type="submit" className="w-full">
                            Guardar cambios
                        </Button>
                    </Form>
                </CardContent>
            </Card>
        </div>
    )
}

I have added a console.log(category) to log whether the category is loaded and it does, also the category.idPadre has data.

I tried adding a state parentCategory in order to handle the select value and event change but I guess it's not a good practice, since probably I am doing something wrong.

I also tried to use ChatGPT but it may overcomplicated things (not working). Converting category.idPadre into a string was an approach the LLM decided to use, but it didn't work either.

Any suggestions?

Share Improve this question asked Feb 11 at 21:27 MaramalMaramal 3,4749 gold badges59 silver badges101 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 0

I had to use the state to set/get the value:

export default function EditCategoryPage() {
    // ...
    const [parentCategory, setParentCategory] = useState(0)
    // ...
    
    useEffect(() => {
        async fetchData() {
            // ...
            const parentId = Number(catData?.idPadre)
            if (!isNaN(parentId)) {
                setParentCategory(parentId)
            }
        }
        fetchData()
    }, [categoryId])

    return (
        <div>
            <Form>
                <select
                    className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
                    key={fields.idPadre.key}
                    name={fields.idPadre.name}
                    value={parentCategory}
                    onChange={event => 
                        setParentCategory(Number(event.target.value))}
                    id="idPadre"
                    disabled={categories.length <= 1}
                >
                    <option value="">
                        {categories.length > 1 ? "Seleccionar categoría" : "No hay categorías"}
                    </option>
                    {categories
                        .filter(cat => cat.id !== category.id)
                        .map((cat) => (
                            <option key={cat.id} value={cat.id.toString()}>
                                {cat.nombre}
                            </option>
                        ))}
                </select>
            </Form>
        </div>
    )
}

In order for your example to work, you should delay rendering of select element.

It might be done with adding guard condition categories.length > 0 to your layout.

Your sample doesn't work because at the moment when the component is rendering the first time, the value from defaultValue property is still missing from categories variable. The effect, which populates the property is still not completed and the defaultValue property is applied only on first render.

Working example:

...

{
    categories.length > 0 && (
        <select
            className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
            key={fields.idPadre.key}
            name={fields.idPadre.name}
            // Convertimos a string para que coincida con el value de las opciones
            defaultValue={category?.idPadre ? category.idPadre.toString() : ""}
            id="idPadre"
            disabled={categories.length <= 1}>
            <option value="">
                {categories.length > 1 ? "Seleccionar categoría" : "No hay categorías"}
            </option>
            {categories.filter(cat => cat.id !== category.id)
                .map((cat) => (
                    <option key={cat.id} value={cat.id.toString()}>
                        {cat.nombre}
                    </option>
                ))}
        </select>)
}

...

}

本文标签: reactjsNextJSConform Select not populated after loading dataStack Overflow