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,