diff --git a/packages/ts/react-crud/test/autocrud.spec.tsx b/packages/ts/react-crud/test/autocrud.spec.tsx
index 46db6b3732..decc6f5d91 100644
--- a/packages/ts/react-crud/test/autocrud.spec.tsx
+++ b/packages/ts/react-crud/test/autocrud.spec.tsx
@@ -12,7 +12,14 @@ import ConfirmDialogController from './ConfirmDialogController.js';
import { CrudController } from './CrudController.js';
import FormController from './FormController';
import GridController from './GridController';
-import { getItem, PersonModel, personService } from './test-models-and-services.js';
+import {
+ createService,
+ getItem,
+ PersonModel,
+ personService,
+ type UserData,
+ UserDataModel,
+} from './test-models-and-services.js';
use(sinonChai);
use(chaiDom);
@@ -325,6 +332,23 @@ describe('@vaadin/hilla-react-crud', () => {
expect((await form.getField('Last name')).value).to.equal('');
});
+ it('opens the form in a dialog when row has undefined value', async () => {
+ const nullData: UserData[] = [
+ {
+ id: 1,
+ name: undefined,
+ },
+ ];
+ const customService = createService(nullData);
+ const result = render();
+ const grid = await GridController.init(result, user);
+ await grid.toggleRowSelected(0);
+
+ const form = await FormController.init(user);
+ expect(form.instance).to.exist;
+ expect((await form.getField('Name')).value).to.equal(undefined);
+ });
+
it('closes the dialog when clicking close button', async () => {
const result = render();
const grid = await GridController.init(result, user);
diff --git a/packages/ts/react-crud/test/test-models-and-services.ts b/packages/ts/react-crud/test/test-models-and-services.ts
index 3555f3bb5b..021574a0de 100644
--- a/packages/ts/react-crud/test/test-models-and-services.ts
+++ b/packages/ts/react-crud/test/test-models-and-services.ts
@@ -39,7 +39,7 @@ export interface Address {
}
export interface Department extends HasIdVersion {
- name: string;
+ name?: string;
}
export interface Person extends HasIdVersion, Named {
@@ -74,6 +74,10 @@ export interface ColumnRendererTestValues extends HasIdVersion {
nested?: NestedTestValues;
}
+export interface UserData extends HasIdVersion {
+ name?: string;
+}
+
class GenderModel extends EnumModel {
static override createEmptyValue = makeEnumEmptyValueCreator(GenderModel);
readonly [_enum] = Gender;
@@ -349,6 +353,30 @@ export class ColumnRendererTestModel<
}
}
+export class UserDataModel extends ObjectModel {
+ static override createEmptyValue = makeObjectEmptyValueCreator(UserDataModel);
+
+ get id(): NumberModel {
+ return this[_getPropertyModel](
+ 'id',
+ (parent, key) =>
+ new NumberModel(parent, key, true, { meta: { annotations: [{ name: 'jakarta.persistence.Id' }] } }),
+ );
+ }
+
+ get version(): NumberModel {
+ return this[_getPropertyModel](
+ 'version',
+ (parent, key) =>
+ new NumberModel(parent, key, true, { meta: { annotations: [{ name: 'jakarta.persistence.Version' }] } }),
+ );
+ }
+
+ get name(): StringModel {
+ return this[_getPropertyModel]('name', (parent, key) => new StringModel(parent, key, true));
+ }
+}
+
type HasIdVersion = {
id?: number;
version?: number;
diff --git a/packages/ts/react-form/src/index.ts b/packages/ts/react-form/src/index.ts
index 70a2b16368..b2c9a468f5 100644
--- a/packages/ts/react-form/src/index.ts
+++ b/packages/ts/react-form/src/index.ts
@@ -26,9 +26,16 @@ import type { Writable } from 'type-fest';
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
__REGISTER__();
+let isRendering = false;
+
function useUpdate() {
- const [_, update] = useReducer((x: number) => x + 1, 0);
- return update;
+ const [_, count] = useReducer((x: number) => x + 1, 0);
+ return () => {
+ if (isRendering) {
+ return;
+ }
+ count();
+ };
}
export type FieldDirectiveResult = Readonly<{
@@ -128,6 +135,7 @@ function useFields(node: BinderNode): FieldDirective
const registry = new WeakMap();
return ((model: AbstractModel) => {
+ isRendering = true;
const n = getBinderNode(model);
let fieldState = registry.get(model);
@@ -211,6 +219,7 @@ function useFields(node: BinderNode): FieldDirective
fieldState.strategy.invalid = n.invalid;
}
+ isRendering = false;
return {
name: n.name,
ref: fieldState.ref,