Form Binding (FirstNameComponent example) renders "undefined" as input value

I’m trying to create my own Form Components (Text-Input, Select-Boxes, etc.)

I copied the “FullNameComponent” from the docs - when using it as it is it renders the String “undefined” as the content of the <input>-Element.

Here’s the full code:

import type { ViewConfig } from "@vaadin/hilla-file-router/types.js";
import {useForm, useFormPart} from "@vaadin/hilla-react-form";
import PersonModel from "Frontend/generated/org/vaadin/example/model/PersonModel";
import {StringModel} from "@vaadin/hilla-lit-form";

interface FullNameProps {
  fullNameModel: StringModel;
}

export default function MainView() {
  const {model, field, value} = useForm(PersonModel)

  return (
    <>
      {/* Question: why does it write "undefined" as the value into the <input />  */}
      <FullNameComponent fullNameModel={model.name} />

      <pre>{JSON.stringify(value, null, 2)}</pre>
    </>
  );
}

function FullNameComponent({fullNameModel}: FullNameProps) {
  const {model, field, required, errors, invalid, value, setValue} = useFormPart(fullNameModel);

  return (
    <>
      <label htmlFor="fullName">
        Full name
        {required ? '*' : ''}
      </label>
      <input id="fullName" {...field(model)}></input>
      <br/>
      <span className="label" style={{visibility: invalid ? 'visible' : 'hidden'}}>
          <strong>
           {errors[0]?.message}
          </strong>
        </span>
    </>
  );
}

Here’s the Person.java

package org.vaadin.example.model;

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Hi @Pascalmh,
Thanks for sharing this. Could you please share some info about which version of Hilla you’re using? I used your shared code and couldn’t reproduce this e.g. with 24.4.8 nor 24.5.0.alpha9 (latest alpha).

I’m using Vaadin 24.4.8.

I’ve created a bug on the hilla-repo, there’s a link to a repo with the reproduction attached:

1 Like

OK, after looking at your repo, now I can tell why I couldn’t reproduce it. My test app had a package-info.java containing import org.springframework.lang.NonNullApi; in entity package, that makes the constructor and setter parameter to be implicitly @NotNull.

This tells the generator that the name is not optional in the generated PersonModel:

get name(): StringModel_1 {
    return this[_getPropertyModel_1]("name", (parent, key) => 
           new StringModel_1(parent, key, false,  // <= here false means not optional
                  { meta: { javaType: "java.lang.String" } }));
}

while removing the package-info.java makes it nullable, the generated model is changed to this:

get name(): StringModel_1 {
    return this[_getPropertyModel_1]("name", (parent, key) => 
           new StringModel_1(parent, key, true,  // <= true means optional
                  { meta: { javaType: "java.lang.String" } }));
}

And then in the form, the useForm(PersonModel) will try to create an empty Person object for the form bindings, and it executes the following:

export function makeObjectEmptyValueCreator<M extends ObjectModel>(type: DetachedModelConstructor<M>): () => Value<M> {
  const model = createDetachedModel(type);

  return () => {
    const obj: Partial<Value<M>> = {};

    // Iterate the model class hierarchy up to the ObjectModel, and extract
    // the property getter names from every prototypes
    for (const [key, getter] of getObjectModelOwnAndParentGetters(model)) {
      const propertyModel = getter.call(model);
      obj[key] = (
        propertyModel[_optional] ? undefined : propertyModel.constructor.createEmptyValue()
      ) as Value<M>[keyof Value<M>];
    }

    return obj as Value<M>;
  };
}

So, according to the generated PersonModel, the propertyModel[_optional] is deciding between the undefined and string’s empty value creator that resolves to "".

Following the previous comment, marking the Person properties as @Nonnull fixes the issue as the string’s empty value will be used instead of undefined.

package org.vaadin.example.model;

import com.vaadin.hilla.Nonnull;

public class Person {

    @Nonnull
    private String name;

    public Person(@Nonnull String name) {
        this.name = name;
    }

    public void setName(@Nonnull String name) {
        this.name = name;
    }

    public @Nonnull String getName() {
        return name;
    }
}

Thank you for the replies!

But what about the scenarios where you actually have a field or fields that are possibly not there.

Let’s say I’m selling Music and you can either download it or I will ship a CD to you.
If you decide you want the CD you’ll need to provide your address (otherwise you don’t => nullable)

So there would be something like this

public class Order {
  private String productId;
  private Integer quantity;
  private Boolean isDownload; // checkbox in the frontend
  @Nullable
  private Location shippingAddress;
}

On the Location I might want to validate that the StreetName has at least 3 characters - so there might be more constraints but they should only be checked when isDownload is true.