Skip to content

v0.0.19

Choose a tag to compare

@TrueWinter TrueWinter released this 11 Jun 17:12
· 88 commits to main since this release

This release contains breaking changes.

  • Inputs are now declaritive instead of imperative.
  • Removed the serializeSettings and deserializeSettings methods - settings are now required to use JSON
  • Added helper hooks to support the new declaritive pattern
  • The validateSettings input method no longer requires deserializing settings
  • The Key Value Input now uses an array for the value ([string, string][]), simplifying logic and fixing edge cases. The renderHtml method still returns a key-value object. While the Input will accept both data formats, supplying a key-value object is deprecated in favour of the array format and will be removed in a future release.
  • If a server-side error occurs, this is now returned in the response in development environments
Required Changes (Example)
 interface TextInputSettings {
   maxLength: number
   required: boolean
 }

 const input: Input<string, TextInputSettings> = {
   id: 'text',
   name: 'Text',

   serialize: (data) => data,
   deserialize: (data) => data,

-  renderInput: () => forwardRef((props, ref) => {
-    const { maxLength, required } = props.settings;
-    const field = useField({
-      mode: 'uncontrolled',
-      initialValue: props.value || '',
-      validateOnChange: true,
-      validate: (value) => {
-        if (required && !value) return `${props.name} is required`;
-        if (maxLength && maxLength !== 0 && value.length > maxLength) {
-          return `${props.name} has a maximum length of ${maxLength}`;
-        }
-        return null;
-      },
-      onValueChange: props.notifyChanges
-    });
-
-    useImperativeHandle(ref, () => ({
-      getValues: () => field.getValue(),
-      hasError: async () => !!(await field.validate())
-    }));
+  renderInput: ({
+    name, description, settings, value, onChange, registerValidator, unregisterValidator
+  }) => {
+    const { maxLength, required } = settings;
+    const error = useInputValidator((v) => {
+      if (required && !v) return `${name} is required`;
+      if (maxLength && maxLength !== 0 && v.length > maxLength) {
+        return `${name} has a maximum length of ${maxLength}`;
+      }
+      return null;
+    }, registerValidator, unregisterValidator);

     return (
-      <TextInput label={props.name} description={props.description} required={required}
-        maxLength={maxLength > 1 ? maxLength : null} {...field.getInputProps()} />
+      <TextInput
+        label={name}
+        description={description}
+        required={required}
+        maxLength={maxLength > 1 ? maxLength : null}
+        error={error}
+        value={value}
+        onChange={(v) => onChange(v.target.value)} />
     );
-  }),
-
-  serializeSettings: (data) => JSON.stringify(data),
-  deserializeSettings: (data) => JSON.parse(data),
-
-  renderSettings: () => forwardRef((props, ref) => {
-    const form = useForm({
-      mode: 'uncontrolled',
-      initialValues: {
-        maxLength: props.settings?.maxLength || 0,
-        required: props.settings?.required ?? true
-      },
-      validateInputOnChange: true,
-      validate: (values) => ({
-        maxLength: values.maxLength < 0 ? 'Max length must be positive' : null
-      }),
-    });
+  },

-    useImperativeHandle(ref, () => ({
-      getValues: () => form.getValues(),
-      hasError: () => form.validate().hasErrors
-    }));
+  renderSettings: ({ settings, onChange, registerValidator, unregisterValidator }) => {
+    const errors = useInputValidator<TextInputSettings>(
+      (v) => ({
+        maxLength: v.maxLength < 0 ? 'Max length must be positive' : null
+      }),
+      registerValidator,
+      unregisterValidator
+    );

-    useEffect(() => {
-      form.validate();
-    }, []);
+    const [merged, changeSetting] = useSettingsHandler({
+      maxLength: settings?.maxLength || 0,
+      required: settings?.required ?? true
+    }, settings, onChange);

     return (
       <Stack>
         <NumberInput label="Max Length" allowDecimal={false}
           description="Set to 0 to disable length limit" required
-          {...form.getInputProps('maxLength')} key={form.key('maxLength')} />
-        <Checkbox label="Required" {...form.getInputProps('required', { type: 'checkbox' })}
-          key={form.key('required')} />
+          error={errors?.maxLength} value={merged.maxLength}
+          onChange={(v: number) => changeSetting('maxLength', v)} />
+        <Checkbox label="Required" error={errors?.required}
+          checked={merged.required}
+          onChange={(v) => changeSetting('required', v.target.checked)} />
       </Stack>
     );
-  }),
+  },

   validate: (stringifiedValue, deserialize, settings) => {
     if (settings.required && !stringifiedValue) {
       throw new ExpressError('Required Text Input does not have a value');
     }

     const value = deserialize(stringifiedValue);

     if (settings.maxLength > 0 && value.length > settings.maxLength) {
       throw new ExpressError('Text Input value is longer than allowed');
     }
   },

-  validateSettings: (serializedSettings, deserialize) => {
-    if (!serializedSettings) {
+  validateSettings: (settings) => {
+    if (!settings) {
       throw new ExpressError('Settings are required');
     }

-    const settings = deserialize(serializedSettings);
-
     if (typeof settings.maxLength !== 'number') {
       throw new ExpressError('Max Length must be a number');
     }

     if (settings.maxLength < 0) {
       throw new ExpressError('Max Length cannot be negative');
     }

     if (typeof settings.required !== 'boolean') {
       throw new ExpressError('Required must be a boolean');
     }
   },

   renderHtml: (value) => value
 };

 export default input;