From f1a85e1163538027d7d3b00a108f6a8e9e15b597 Mon Sep 17 00:00:00 2001 From: Michal Kurz Date: Tue, 23 May 2023 20:10:56 +0200 Subject: [PATCH] WIP: MNP of debounced validation --- packages/antd/package.json | 1 - packages/bootstrap-4/package.json | 2 +- packages/chakra-ui/package.json | 2 +- packages/core/package.json | 1 - packages/core/src/components/Form.tsx | 145 +++++++++++------- packages/docs/package.json | 2 +- packages/fluent-ui/package.json | 2 +- packages/material-ui/package.json | 2 +- packages/mui/package.json | 2 +- packages/playground/package.json | 3 +- .../playground/src/components/Playground.tsx | 2 + packages/semantic-ui/package.json | 2 +- packages/utils/package.json | 2 +- packages/validator-ajv6/package.json | 2 +- packages/validator-ajv8/package.json | 2 +- 15 files changed, 103 insertions(+), 69 deletions(-) diff --git a/packages/antd/package.json b/packages/antd/package.json index 52422e3b48..3e2e352fa6 100644 --- a/packages/antd/package.json +++ b/packages/antd/package.json @@ -10,7 +10,6 @@ "cs-check": "prettier -l \"{src,test}/**/*.ts?(x)\"", "cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write", "lint": "eslint src test", - "precommit": "lint-staged", "test": "dts test", "test:update": "dts test --u", "bump-packages": "npm update --save --lockfile-version 2" diff --git a/packages/bootstrap-4/package.json b/packages/bootstrap-4/package.json index 8207872494..b21e644d43 100644 --- a/packages/bootstrap-4/package.json +++ b/packages/bootstrap-4/package.json @@ -17,7 +17,7 @@ "cs-check": "prettier -l \"{src,test}/**/*.ts?(x)\"", "cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write", "lint": "eslint src test", - "precommit": "lint-staged", + "test": "dts test", "test:update": "dts test --u", "bump-packages": "npm update --save --lockfile-version 2" diff --git a/packages/chakra-ui/package.json b/packages/chakra-ui/package.json index 5b2458c286..80db49b618 100644 --- a/packages/chakra-ui/package.json +++ b/packages/chakra-ui/package.json @@ -13,7 +13,7 @@ "cs-check": "prettier -l \"{src,test}/**/*.ts?(x)\"", "cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write", "lint": "eslint src test", - "precommit": "lint-staged", + "test": "dts test", "test:update": "dts test --u", "test:watch": "dts test --watch", diff --git a/packages/core/package.json b/packages/core/package.json index 0356efdb9c..881d47d90d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -7,7 +7,6 @@ "cs-check": "prettier -l \"{src,test,testSnap}/**/*.[jt]s?(x)\"", "cs-format": "prettier \"{src,test,testSnap}/**/*.[jt]s?(x)\" --write", "lint": "eslint src test", - "precommit": "lint-staged", "publish-to-npm": "npm run build && npm publish", "test": "dts test", "test:debug": "node --inspect-brk node_modules/.bin/dts test", diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 24321a40a4..8bb216eb19 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -38,11 +38,14 @@ import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; import _pick from 'lodash/pick'; import _toPath from 'lodash/toPath'; +import _debounce from 'lodash/debounce'; +import _memoize from 'lodash/memoize'; import getDefaultRegistry from '../getDefaultRegistry'; /** The properties that are passed to the `Form` */ export interface FormProps { + enableLogging?: boolean; /** The JSON schema object for the form */ schema: S; /** An implementation of the `ValidatorType` interface that is needed for form validation to work */ @@ -156,7 +159,7 @@ export interface FormProps { + if (this.props.enableLogging) { + console.log(...params); + } + }; + /** React lifecycle method that gets called before new props are provided, updates the state based on new props. It * will also call the`onChange` handler if the `formData` is modified to add missing default values as part of the * state construction. @@ -285,14 +296,30 @@ export default class Form< */ UNSAFE_componentWillReceiveProps(nextProps: FormProps) { const nextState = this.getStateFromProps(nextProps, nextProps.formData); - if ( + const mustValidate = !nextProps.noValidate && nextProps.liveValidate; + const shouldCallOnChange = !deepEquals(nextState.formData, nextProps.formData) && !deepEquals(nextState.formData, this.state.formData) && - nextProps.onChange - ) { - nextProps.onChange(nextState); + nextProps.onChange; + + if (shouldCallOnChange) { + nextProps.onChange!(nextState); } + this.setState(nextState); + + if (mustValidate) { + // @ts-ignore + const debounceThreshold = nextProps.liveValidate?.debounceThreshold; + + const callbackAfterValidation = () => { + if (shouldCallOnChange) { + nextProps.onChange!(this.state); + } + }; + + this.validateAndUpdateStateDebounced(debounceThreshold)(nextState.formData, callbackAfterValidation); + } } /** Extracts the updated state from the given `props` and `inputFormData`. As part of this process, the @@ -308,8 +335,6 @@ export default class Form< const schema = 'schema' in props ? props.schema : this.props.schema; const uiSchema: UiSchema = ('uiSchema' in props ? props.uiSchema! : this.props.uiSchema!) || {}; const edit = typeof inputFormData !== 'undefined'; - const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate; - const mustValidate = edit && !props.noValidate && liveValidate; const rootSchema = schema; const experimental_defaultFormStateBehavior = 'experimental_defaultFormStateBehavior' in props @@ -328,38 +353,25 @@ export default class Form< const getCurrentErrors = (): ValidationData => { if (props.noValidate) { return { errors: [], errorSchema: {} }; - } else if (!props.liveValidate) { - return { - errors: state.schemaValidationErrors || [], - errorSchema: state.schemaValidationErrorSchema || {}, - }; } + + // return { + // errors: state.schemaValidationErrors || [], + // errorSchema: state.schemaValidationErrorSchema || {}, + // }; + return { errors: state.errors || [], errorSchema: state.errorSchema || {}, }; }; - let errors: RJSFValidationError[]; - let errorSchema: ErrorSchema | undefined; - let schemaValidationErrors: RJSFValidationError[] = state.schemaValidationErrors; - let schemaValidationErrorSchema: ErrorSchema = state.schemaValidationErrorSchema; - if (mustValidate) { - const schemaValidation = this.validate(formData, schema, schemaUtils); - errors = schemaValidation.errors; - errorSchema = schemaValidation.errorSchema; - schemaValidationErrors = errors; - schemaValidationErrorSchema = errorSchema; - } else { - const currentErrors = getCurrentErrors(); - errors = currentErrors.errors; - errorSchema = currentErrors.errorSchema; - } - if (props.extraErrors) { - const merged = validationDataMerge({ errorSchema, errors }, props.extraErrors); - errorSchema = merged.errorSchema; - errors = merged.errors; - } + const currentErrors = getCurrentErrors(); + const errors: RJSFValidationError[] = currentErrors.errors; + const errorSchema: ErrorSchema | undefined = currentErrors.errorSchema; + const schemaValidationErrors: RJSFValidationError[] = state.schemaValidationErrors; + const schemaValidationErrorSchema: ErrorSchema = state.schemaValidationErrorSchema; + const idSchema = schemaUtils.toIdSchema( retrievedSchema, uiSchema['ui:rootFieldId'], @@ -508,7 +520,6 @@ export default class Form< const mustValidate = !noValidate && liveValidate; let state: Partial> = { formData, schema }; - let newFormData = formData; if (omitExtraData === true && liveOmit === true) { const retrievedSchema = schemaUtils.retrieveSchema(schema, formData); @@ -516,41 +527,63 @@ export default class Form< const fieldNames = this.getFieldNames(pathSchema, formData); - newFormData = this.getUsedFormData(formData, fieldNames); state = { - formData: newFormData, + formData: this.getUsedFormData(formData, fieldNames), }; } - if (mustValidate) { - const schemaValidation = this.validate(newFormData); - let errors = schemaValidation.errors; - let errorSchema = schemaValidation.errorSchema; - const schemaValidationErrors = errors; - const schemaValidationErrorSchema = errorSchema; - if (extraErrors) { - const merged = validationDataMerge(schemaValidation, extraErrors); - errorSchema = merged.errorSchema; - errors = merged.errors; - } - state = { - formData: newFormData, - errors, - errorSchema, - schemaValidationErrors, - schemaValidationErrorSchema, - }; - } else if (!noValidate && newErrorSchema) { + if (!noValidate && newErrorSchema) { const errorSchema = extraErrors ? (mergeObjects(newErrorSchema, extraErrors, 'preventDuplicates') as ErrorSchema) : newErrorSchema; + state = { - formData: newFormData, + formData, errorSchema: errorSchema, errors: toErrorList(errorSchema), }; } - this.setState(state as FormState, () => onChange && onChange({ ...this.state, ...state }, id)); + + this.setState(state as FormState, () => { + onChange && onChange({ ...this.state, ...state }, id); + + if (mustValidate) { + // @ts-ignore + const debounceThreshold = liveValidate?.debounceThreshold; + const callbackAfterValidation = () => this.props.onChange?.(this.state, id); + this.validateAndUpdateStateDebounced(debounceThreshold)(state.formData, callbackAfterValidation); + } + }); + }; + + validateAndUpdateStateDebounced = _memoize((debounceThreshold: number | null | undefined) => { + return typeof debounceThreshold === 'number' + ? (_debounce(this.validateAndUpdateState, debounceThreshold) as any) + : this.validateAndUpdateState; + }); + + validateAndUpdateState = (formData: T | undefined, callback?: () => void) => { + this.log('RUNNING VALIDATE!!', this.props.liveValidate); + const schemaValidation = this.validate(formData); + let errors = schemaValidation.errors; + let errorSchema = schemaValidation.errorSchema; + const schemaValidationErrors = errors; + const schemaValidationErrorSchema = errorSchema; + + if (this.props.extraErrors) { + const merged = validationDataMerge(schemaValidation, this.props.extraErrors); + errorSchema = merged.errorSchema; + errors = merged.errors; + } + + const state = { + errors, + errorSchema, + schemaValidationErrors, + schemaValidationErrorSchema, + }; + + this.setState(state as FormState, callback); }; /** diff --git a/packages/docs/package.json b/packages/docs/package.json index c9fea4b2b2..c2e11df46e 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -13,7 +13,7 @@ "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", "typecheck": "tsc", - "precommit": "lint-staged", + "cs-check": "prettier -l \"{src, docs}/**/*.(ts?(x)|md|css)\"", "cs-format": "prettier \"{src, docs}/**/*.(ts?(x)|md|css)\" --write", "bump-packages": "npm update --save --lockfile-version 2" diff --git a/packages/fluent-ui/package.json b/packages/fluent-ui/package.json index db6a987199..590e40ec55 100644 --- a/packages/fluent-ui/package.json +++ b/packages/fluent-ui/package.json @@ -13,7 +13,7 @@ "cs-check": "prettier -l \"{src,test}/**/*.ts?(x)\"", "cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write", "lint": "eslint src test", - "precommit": "lint-staged", + "test": "dts test", "test:update": "dts test --u", "bump-packages": "npm update --save --lockfile-version 2" diff --git a/packages/material-ui/package.json b/packages/material-ui/package.json index 663eb7699f..8736fac693 100644 --- a/packages/material-ui/package.json +++ b/packages/material-ui/package.json @@ -13,7 +13,7 @@ "cs-check": "prettier -l \"{src,test}/**/*.ts?(x)\"", "cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write", "lint": "eslint src test", - "precommit": "lint-staged", + "test": "dts test", "test:update": "dts test --u", "bump-packages": "npm update --save --lockfile-version 2" diff --git a/packages/mui/package.json b/packages/mui/package.json index d576b9a0fb..c8c3643943 100644 --- a/packages/mui/package.json +++ b/packages/mui/package.json @@ -13,7 +13,7 @@ "cs-check": "prettier -l \"{src,test}/**/*.ts?(x)\"", "cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write", "lint": "eslint src test", - "precommit": "lint-staged", + "test": "dts test", "test:update": "dts test --u", "bump-packages": "npm update --save --lockfile-version 2" diff --git a/packages/playground/package.json b/packages/playground/package.json index d7d5d05fe1..b2a5743958 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -12,10 +12,11 @@ "cs-format": "prettier \"src/**/*.ts?(x)\" --write", "build": "rimraf build && cross-env NODE_ENV=production vite build", "lint": "eslint src", - "precommit": "lint-staged", + "publish-to-gh-pages": "npm run build && gh-pages --dist build/", "publish-to-npm": "npm run build && npm publish", "start": "vite --force", + "restart": "cd ../.. && npm run build && cd packages/playground && npm run start", "preview": "vite preview", "bump-packages": "npm update --save --lockfile-version 2" }, diff --git a/packages/playground/src/components/Playground.tsx b/packages/playground/src/components/Playground.tsx index 4a7d31c5a4..91fe75a3ce 100644 --- a/packages/playground/src/components/Playground.tsx +++ b/packages/playground/src/components/Playground.tsx @@ -195,9 +195,11 @@ export default function Playground({ themes, validators }: PlaygroundProps) { >