diff --git a/examples/src/Playground.js b/examples/src/Playground.js index fac8f2c..a25fb22 100644 --- a/examples/src/Playground.js +++ b/examples/src/Playground.js @@ -77,7 +77,7 @@ export const Playground = () => { setSchema(e) }} /> - {/* { try { @@ -87,9 +87,9 @@ export const Playground = () => { } }} value={typeof schema === 'object' ? JSON.stringify(schema, null, 2) : schema} - /> */} + /> - {/* { try { @@ -97,7 +97,7 @@ export const Playground = () => { } catch (_) { } }} value={typeof value === 'object' ? JSON.stringify(value, null, 2) : value} - /> */} + />
diff --git a/src/constraints.js b/src/constraints.js index 5974ff8..4d98ab7 100644 --- a/src/constraints.js +++ b/src/constraints.js @@ -60,7 +60,7 @@ export const maxSize = (ref, message = `size is excedeed ${ref}`) => (r) => { //mixed export const nullable = () => (r) => r.nullable().optional().transform(v => { const { type } = r.describe() - switch (type) { + switch (type) { case 'string': case 'number': return (v || '').toString().length <= 0 ? null : v @@ -86,6 +86,10 @@ export const when = (ref, test, then = [], otherwise = []) => (r, key, dependenc } export const oneOf = (arrayOfValues, message = `This value must be one of ${arrayOfValues.join(', ')}`) => (r) => r.oneOf(arrayOfValues.map(maybeRef), message) +export const blacklist = (arrayOfValues, message = `This value can't include the following values ${arrayOfValues.join(', ')}`) => (r) => r.test('blacklist' + Date.now(), message, value => { + return !arrayOfValues.some(f => value.includes(f)) +}) + export const ref = (ref) => yup.ref(ref) const maybeRef = (x) => x?.ref ? ref(x.ref) : x @@ -112,6 +116,7 @@ export const jsonConstraints = { test: (c) => test(c.name, c.message, c.test), when: ({ ref, test, then = [], otherwise = [] }) => when(ref, test, then, otherwise), oneOf: ({ arrayOfValues, message = `This value must be one of ${arrayOfValues.join(', ')}` }) => oneOf(arrayOfValues, message), + blacklist: ({ arrayOfValues, message = `This value can't include the following values ${arrayOfValues.join(', ')}` }) => blacklist(arrayOfValues, message), ref: (val) => ref(val.ref), nullable: () => nullable() } \ No newline at end of file diff --git a/src/controlledInput.js b/src/controlledInput.js index 5294e94..09662e4 100644 --- a/src/controlledInput.js +++ b/src/controlledInput.js @@ -43,6 +43,8 @@ export const ControlledInput = ({ defaultValue, step, entry, children, component } const error = entry.split('.').reduce((acc, curr) => acc && acc[curr], errors) + + console.log("render controlled input " + entry) return ( { }) } +const Watcher = ({ options, control, schema }) => { + const data = useWatch({ control }); + + if (options.watch) { + if (typeof options.watch === 'function') { + options.watch(cleanOutputArray(data, schema)) + } else { + console.group('react-form watch') + console.log(cleanOutputArray(data, schema)) + console.groupEnd() + } + } + + return null +} + export const Form = React.forwardRef(({ schema, flow, value, inputWrapper, onSubmit, onError = () => { }, footer, style = {}, className, options = {} }, ref) => { const classes = useCustomStyle(style) const formFlow = flow || Object.keys(schema) @@ -186,44 +202,30 @@ export const Form = React.forwardRef(({ schema, flow, value, inputWrapper, onSub resolver: (data, context, options) => yupResolver(resolver(data))(data, context, options), defaultValues: cleanInputArray(value, defaultValues, flow, schema), shouldFocusError: false, - mode: 'onChange' + mode: 'onSubmit' // onChange triggers re-rendering }); - const { handleSubmit, formState: { errors, dirtyFields }, reset, watch, trigger, getValues } = methods + const { handleSubmit, formState: { errors, dirtyFields }, reset, trigger, getValues } = methods useEffect(() => { + console.log('re-render cauz trigger') trigger() }, [trigger]) useEffect(() => { - if (value) { - console.log('reset from value/reset effect') - reset(cleanInputArray(value, defaultValues, flow, schema)) - } + console.log('re-render cauz value/reset changed') + reset(cleanInputArray(value, defaultValues, flow, schema)) }, [value, reset]) useEffect(() => { - console.log('reset from schema effect') + console.log('re-render cauz schema changed') reset(cleanInputArray(value, defaultValues, flow, schema)) }, [schema]) - const data = watch(); - if (!!options.autosubmit) { handleSubmit(data => onSubmit(cleanOutputArray(data, schema)), onError)() } - if (options.watch) { - if (typeof options.watch === 'function') { - console.log('call watch') - options.watch(cleanOutputArray(data, schema)) - } else { - console.group('react-form watch') - console.log(cleanOutputArray(data, schema)) - console.groupEnd() - } - } - const functionalProperty = (entry, prop) => { if (typeof prop === 'function') { return prop({ rawValues: getValues(), value: getValues(entry) }); @@ -243,7 +245,8 @@ export const Form = React.forwardRef(({ schema, flow, value, inputWrapper, onSub })); return ( - + +
{ const clean = cleanOutputArray(data, schema) onSubmit(clean) @@ -264,6 +267,7 @@ export const Form = React.forwardRef(({ schema, flow, value, inputWrapper, onSub .map(visible => { switch (typeof visible) { case 'object': + console.log('watch from visibleStep') const value = watch(step.visible.ref); return option(step.visible.test).map(test => test(value, idx)).getOrElse(value) case 'boolean': @@ -314,6 +318,8 @@ const Step = ({ entry, realEntry, step, schema, inputWrapper, httpClient, defaul const classes = useCustomStyle(); const { formState: { errors, dirtyFields, touchedFields, isSubmitted }, control, trigger, getValues, setValue, watch, register } = useFormContext(); + console.log("re-render : " + entry) + if (entry && typeof entry === 'object') { const errored = entry.flow.some(step => !!errors[step] && (dirtyFields[step] || touchedFields[step])) return ( @@ -334,6 +340,7 @@ const Step = ({ entry, realEntry, step, schema, inputWrapper, httpClient, defaul .map(visible => { switch (typeof visible) { case 'object': + console.log("watch of collapse") const value = watch(visible.ref); return option(visible.test).map(test => test(value, index)).getOrElse(value) case 'boolean': @@ -365,19 +372,30 @@ const Step = ({ entry, realEntry, step, schema, inputWrapper, httpClient, defaul const isTouched = entry.split('.').reduce((acc, curr) => acc && acc[curr], touchedFields) const errorDisplayed = !!error && (isSubmitted || isDirty || isTouched) - const data = watch() - const newData = cleanOutputArray(data, schema) const onAfterChangeFunc = onAfterChange || step.onAfterChange || step.on_after_change if (onAfterChangeFunc) { - onAfterChangeFunc({ - entry, - value: getValues(entry), - getValue: e => getValues(e), - rawValues: newData, - setValue, - onChange: v => setValue(entry, v) - }) + const data = watch() + + const d = entry + .replace('[', '.').replace(']', '') + .split('.') + .reduce((acc, curr) => acc && acc[curr], data) || {} + + const currentData = usePrevious(cleanOutputArray(d, schema)) + + const newData = cleanOutputArray(d, schema) + + if (!deepEqual(newData, currentData)) + onAfterChangeFunc({ + entry, + previousValue: currentData, + value: getValues(entry), + getValue: e => getValues(e), + rawValues: newData, + setValue, + onChange: v => setValue(entry, v) + }) } if (step.array) { @@ -539,7 +557,7 @@ const Step = ({ entry, realEntry, step, schema, inputWrapper, httpClient, defaul try { v = JSON.parse(e) } catch (err) { - v = {} + v = e } field.onChange(v) option(step.onChange)