hilla icon indicating copy to clipboard operation
hilla copied to clipboard

Conditionally rendered form fields are not initialized properly by field directive

Open peholmst opened this issue 1 year ago • 0 comments

Describe the bug

When using a form, any conditionally rendered fields will not be properly initialized by the field directive. For example the required indicator might be missing, or the field value not set. See the code in Reproduction for more details.

Expected-behavior

When I read an object into a form, and then later render a field using the field directive, it should be properly initialized, with the required indicator visible (if so configured) and the value set (if present inside the form value).

Reproduction

import {FormLayout} from "@hilla/react-components/FormLayout";
import {useForm, useFormPart} from "@hilla/react-form";
import {useEffect, useState} from "react";
import {
    _getPropertyModel,
    makeObjectEmptyValueCreator,
    NotBlank,
    NumberModel,
    ObjectModel,
    StringModel
} from "@hilla/form";
import {Button} from "@hilla/react-components/Button.js";
import {TextField} from "@hilla/react-components/TextField";

interface ProjectDTO {
    id?: number;
    name: string;
}

class ProjectDTOModel<T extends ProjectDTO> extends ObjectModel<T> {
    static override createEmptyValue = makeObjectEmptyValueCreator(ProjectDTOModel);

    get id(): NumberModel {
        return this[_getPropertyModel]("id", (parent, key) => new NumberModel(parent, key, true, {meta: {javaType: "java.lang.Long"}}));
    }

    get name(): StringModel {
        return this[_getPropertyModel]("name", (parent, key) => new StringModel(parent, key, true, {meta: {javaType: "java.lang.String"}}));
    }
}

export function FormDebugView() {
    const {model, field, value, clear, read} = useForm(ProjectDTOModel);
    const nameField = useFormPart(model.name);

    useEffect(() => {
        nameField.addValidator(new NotBlank({message: "Please enter a name."}));
    }, []);

    const [formVisible, setFormVisible] = useState(false);

    function load() {
        const ts = Date.now();
        read({
            id: ts,
            name: "Test " + ts
        });
    }

    return <>
        <Button onClick={clear}>Clear Form</Button>
        <Button onClick={load}>Load Form</Button>
        <Button onClick={() => setFormVisible(true)}>Show Form</Button>
        <Button onClick={() => setFormVisible(false)}>Hide Form</Button>
        {formVisible && (<FormLayout>
            <TextField label={"Project name"} {...field(model.name)}/>
        </FormLayout>)}
        <p>Form value: {JSON.stringify(value)}</p>
    </>;
}

Play around with the show/hide and load/clear buttons. You will notice the field showing up empty and the required indicators coming and going.

System Info

macOS 14.1.1, Chrome 119.0.6045.159, Hilla 2.4.0

peholmst avatar Nov 24 '23 12:11 peholmst