From 570a4acc5a7dce6f1af0fae1804a3d21a18a8df3 Mon Sep 17 00:00:00 2001 From: Damien Pellier Date: Wed, 20 Nov 2024 16:49:43 +0100 Subject: [PATCH] feat(examples): add react-hook-form example & update formik example --- packages/examples/react-webpack/package.json | 1 + .../examples/react-webpack/src/app/App.tsx | 8 +- .../app/components/formFormik/FormFormik.tsx | 407 +++++++++-------- .../components/formHookForm/FormHookForm.tsx | 409 ++++++++++++++++++ .../components/formHookForm/formHookForm.scss | 5 + .../app/components/formNative/FormNative.tsx | 5 +- yarn.lock | 10 + 7 files changed, 650 insertions(+), 195 deletions(-) create mode 100644 packages/examples/react-webpack/src/app/components/formHookForm/FormHookForm.tsx create mode 100644 packages/examples/react-webpack/src/app/components/formHookForm/formHookForm.scss diff --git a/packages/examples/react-webpack/package.json b/packages/examples/react-webpack/package.json index 39eb97635..61bfffb9d 100644 --- a/packages/examples/react-webpack/package.json +++ b/packages/examples/react-webpack/package.json @@ -15,6 +15,7 @@ "formik": "2.4.6", "react": "18.2.0", "react-dom": "18.2.0", + "react-hook-form": "7.53.2", "react-router": "6.27.0", "yup": "1.4.0" }, diff --git a/packages/examples/react-webpack/src/app/App.tsx b/packages/examples/react-webpack/src/app/App.tsx index 7f5726649..e79f993f8 100644 --- a/packages/examples/react-webpack/src/app/App.tsx +++ b/packages/examples/react-webpack/src/app/App.tsx @@ -3,7 +3,8 @@ import React, { type ReactElement } from 'react'; import styles from './app.scss'; // import { Gallery } from './components/gallery/Gallery'; // import { FormFormik } from './components/formFormik/FormFormik'; -import { FormNative } from './components/formNative/FormNative'; +import { FormHookForm } from './components/formHookForm/FormHookForm'; +// import { FormNative } from './components/formNative/FormNative'; // import { TestModal } from './components/testModal/TestModal'; // import { TestSelect } from './components/testSelect/TestSelect'; @@ -12,9 +13,10 @@ import { FormNative } from './components/formNative/FormNative'; function App(): ReactElement { return (
- {/* */} + {/**/} {/**/} - + + {/**/} {/**/} {/* */} {/**/} diff --git a/packages/examples/react-webpack/src/app/components/formFormik/FormFormik.tsx b/packages/examples/react-webpack/src/app/components/formFormik/FormFormik.tsx index ac82ad011..06a11f274 100644 --- a/packages/examples/react-webpack/src/app/components/formFormik/FormFormik.tsx +++ b/packages/examples/react-webpack/src/app/components/formFormik/FormFormik.tsx @@ -1,15 +1,30 @@ import { ODS_INPUT_TYPE } from '@ovhcloud/ods-components'; import { OdsButton, OdsCheckbox, OdsDatepicker, OdsInput, OdsPassword, OdsPhoneNumber, OdsQuantity, OdsRadio, OdsSelect, OdsSwitch, OdsSwitchItem, OdsTextarea, OdsTimepicker } from '@ovhcloud/ods-components/react'; import { useFormik } from 'formik'; -import React, { type ReactElement } from 'react'; +import React, { type ReactElement, useState } from 'react'; import * as yup from 'yup'; import styles from './formFormik.scss'; -// KO for now as setting the value causes infinite loop -// More tests after this is fixed +type FormData = { + checkboxAlone: string, + checkboxGroup: string, + datepicker: Date, + inputNumber: number, + inputText: string, + password: string, + phoneNumber: string, + phoneNumberWithCountries: string, + quantity: number, + radio: string, + select: string, + switch: string, + textarea: string, + timepicker: string, +} -const validationSchema = yup.object({ - checkbox: yup.string().nullable(),//.required(), +const validationSchema = yup.object({ + checkboxAlone: yup.string().nullable(),//.required(), + checkboxGroup: yup.string().nullable(),//.required(), datepicker: yup.date().nullable(),//.required(), inputNumber: yup.number().nullable(),//.required(), inputText: yup.string().nullable(),//.required(), @@ -17,6 +32,7 @@ const validationSchema = yup.object({ phoneNumber: yup.string().nullable(),//.required(), quantity: yup.number().nullable(),//.required(), radio: yup.string().nullable(),//.required(), + radioTest: yup.string().nullable(),//.required(), select: yup.string().nullable(),//.required(), switch: yup.string().nullable(),//.required(), textarea: yup.string().nullable(),//.required(), @@ -24,32 +40,23 @@ const validationSchema = yup.object({ }); function FormFormik(): ReactElement { - const formik = useFormik({ + const formik = useFormik & { datepicker: string }>({ initialValues: { - checkbox: null, - datepicker: null, - inputNumber: null, - inputText: null, - password: null, - phoneNumber: null, - quantity: null, - radio: null, - select: null, - switch: null, - textarea: null, - timepicker: null, - // checkbox: null, - // datepicker: new Date(), - // inputNumber: null, - // inputText: 'input text', - // password: 'password', - // phoneNumber: '0123456789', - // quantity: null, - // radio: 'radio-1', - // select: 'cat', - // switch: 'option1', - // textarea: 'textarea', - // timepicker: '12:34', + checkboxAlone: 'checkbox alone', + // checkboxGroup: 'checkbox group1,checkbox group2', + checkboxGroup: 'checkbox group1', + datepicker: '01-02-2000', + inputNumber: 33, + inputText: 'default input text', + password: 'default password', + phoneNumber: '0123456789', + phoneNumberWithCountries: '+33123456789', + quantity: 0, + radio: 'radio1', + select: 'cat', + switch: 'switch1', + textarea: 'default textarea', + timepicker: '01:23', }, onSubmit: (values) => { console.log('Formik values', values); @@ -57,203 +64,227 @@ function FormFormik(): ReactElement { validateOnMount: true, validationSchema, }); + const [areAllRequired, setAreAllRequired] = useState(false); - function onCheckboxChange(e: any) { - formik.setFieldValue('checkbox', e.detail.checked ? e.detail.value : null); + function onAllRequiredToggle() { + setAreAllRequired(() => !areAllRequired); } return (
- {/*/!* OK but need custom onChange handler *!/*/} - {/*/!* KO reset *!/*/} - {/*
*/} - {/* */} - {/* */} - {/*
*/} +
+ +
+ +

+ Current configuration: +
+ - All fields required: { areAllRequired.toString() } +

+ +
+ { + formik.setFieldValue('checkboxAlone', e.detail.checked ? 'checkbox alone' : null); + }} + value="checkbox alone" + /> + +
- {/*/!* OK *!/*/} - {/**/} +
+ = 0 } + isRequired={ areAllRequired } + name="checkboxGroup" + onOdsBlur={ formik.handleBlur } + onOdsChange={ (e) => { + formik.setFieldValue('checkboxGroup', e.detail.checked ? e.detail.value : null); + }} + value="checkbox group1" + /> + + + = 0 } + isRequired={ areAllRequired } + name="checkboxGroup" + onOdsBlur={ formik.handleBlur } + onOdsChange={ (e) => { + formik.setFieldValue('checkboxGroup', e.detail.checked ? e.detail.value : null); + }} + value="checkbox group2" + /> + +
- {/*/!* KO? reset to "" instead of null *!/*/} - {/**/} + - {/*/!* OK *!/*/} + + { - console.log(e) - formik.handleChange(e) - }} + onOdsChange={ formik.handleChange } type={ ODS_INPUT_TYPE.text } - // value={ formik.values.inputText } /> - {/*/!* KO? clear trigger input Watch twice *!/*/} - {/*/!* KO style different *!/*/} { - console.log(e) - formik.handleChange(e) - }} - value={ formik.values.password } + onOdsChange={ formik.handleChange } /> - {/*/!* OK *!/*/} - {/*/!* KO style different *!/*/} - {/**/} + - {/*/!* KO reset to null set 0 instead *!/*/} - {/*/!* KO value 0 is not displayed in the input *!/*/} - {/**/} + - {/*/!* OK *!/*/} - {/*
*/} - {/* */} - {/* */} + - {/* */} - {/* */} - {/*
*/} +
+ { + formik.setFieldValue('radio', e.detail.checked ? e.detail.value : null); + }} + value="radio1" + /> + - {/*/!* OK *!/*/} - {/**/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/**/} + { + formik.setFieldValue('radio', e.detail.checked ? e.detail.value : null); + }} + value="radio2" + /> + +
- {/*/!* KO Reset *!/*/} - {/**/} - {/* */} - {/* Option 1*/} - {/* */} + + + + + + + + - {/* */} - {/* Option 2*/} - {/* */} - {/**/} + { + formik.setFieldValue('switch', e.detail.value || ''); + }} + > + + Switch 1 + + + + Switch 2 + + - {/* KO type defaultValue should accept null */} { - console.log(e) - formik.handleChange(e) - }} - value={ formik.values.textarea } // KO loop odsChange event + onOdsChange={ formik.handleChange } /> - {/* KO type defaultValue should accept null */} - {/**/} +

Errors: diff --git a/packages/examples/react-webpack/src/app/components/formHookForm/FormHookForm.tsx b/packages/examples/react-webpack/src/app/components/formHookForm/FormHookForm.tsx new file mode 100644 index 000000000..33a0f6998 --- /dev/null +++ b/packages/examples/react-webpack/src/app/components/formHookForm/FormHookForm.tsx @@ -0,0 +1,409 @@ +import { ODS_INPUT_TYPE } from '@ovhcloud/ods-components'; +import { OdsButton, OdsCheckbox, OdsDatepicker, OdsInput, OdsPassword, OdsPhoneNumber, OdsQuantity, OdsRadio, OdsSelect, OdsSwitch, OdsSwitchItem, OdsTextarea, OdsTimepicker } from '@ovhcloud/ods-components/react'; +import React, { type ReactElement, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import styles from './formHookForm.scss'; + +type FormData = { + checkboxAlone: string, + checkboxGroup: string, + datepicker: Date | string, + inputNumber: number, + inputText: string, + password: string, + phoneNumber: string, + phoneNumberWithCountries: string, + quantity: number, + radio: string, + select: string, + switch: string, + textarea: string, + timepicker: string, +} + +const defaultValue: FormData = { + checkboxAlone: 'checkbox alone', + // checkboxGroup: 'checkbox group1,checkbox group2', + checkboxGroup: 'checkbox group1', + datepicker: '01-02-2000', + inputNumber: 33, + inputText: 'default input text', + password: 'default password', + phoneNumber: '0123456789', + phoneNumberWithCountries: '+33123456789', + quantity: 0, + radio: 'radio1', + select: 'cat', + switch: 'switch1', + textarea: 'default textarea', + timepicker: '01:23', +}; + +// We have to use control instead of register +// Though our web-components behave as uncontrolled component, register expect to react to native event (blur, change) +// and in our case it should react to ods events (odsBlur, odsChange) and we can't customize this using register. +// Careful though using control, as it is meant for controlled component. But in our case we should not pass +// the value attribute as the component will update it internally anyway, this only causes useless events. +function FormHookForm(): ReactElement { + const { + control, + formState: { errors }, + handleSubmit, + setValue, + } = useForm({ + mode: 'onBlur', + defaultValues: defaultValue, + }); + const [areAllRequired, setAreAllRequired] = useState(false); + + function onSubmit(data: FormData): void { + console.log('-- submit --') + console.log(data) + } + + function onAllRequiredToggle() { + setAreAllRequired(() => !areAllRequired); + } + + return ( + +

+ +
+ +

+ Current configuration: +
+ - All fields required: { areAllRequired.toString() } +

+ + +
+ { + setValue(field.name, e.detail.checked ? e.detail.value || '' : ''); + }} + value="checkbox alone" + > + + +
+ } + /> + + +
+ = 0 } + isRequired={ areAllRequired } + name={ field.name } + onOdsBlur={ field.onBlur } + onOdsChange={ (e) => { + setValue(field.name, e.detail.checked ? e.detail.value || '' : ''); + }} + value="checkbox group1" + > + + + + = 0 } + isRequired={ areAllRequired } + name={ field.name } + onOdsBlur={ field.onBlur } + onOdsChange={ (e) => { + setValue(field.name, e.detail.checked ? e.detail.value || '' : ''); + }} + value="checkbox group2" + > + + +
+ } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + +
+ { + setValue(field.name, e.detail.checked ? e.detail.value || '' : ''); + }} + value="radio1" + /> + + + { + setValue(field.name, e.detail.checked ? e.detail.value || '' : ''); + }} + value="radio2" + /> + +
+ } + /> + + + + + + + + + + + } + /> + + + { + // field.onChange(e); // does not work on init as the field is never set as dirty + setValue(field.name, e.detail.value || '', { shouldDirty: true }); + }} + > + + Switch 1 + + + Switch 2 + + + Switch 3 + + + } + /> + + + + } + /> + + + + } + /> + +

+ Errors: +
+ { + Object.entries(errors).map(([key, value]) => ( + + { key }: { value.message } +
+
+ )) + } +

+ +
+ + + +
+ + ); +} + +export { FormHookForm }; diff --git a/packages/examples/react-webpack/src/app/components/formHookForm/formHookForm.scss b/packages/examples/react-webpack/src/app/components/formHookForm/formHookForm.scss new file mode 100644 index 000000000..ba3cb92ff --- /dev/null +++ b/packages/examples/react-webpack/src/app/components/formHookForm/formHookForm.scss @@ -0,0 +1,5 @@ +.form-hook-form { + display: flex; + flex-flow: column; + row-gap: 1rem; +} diff --git a/packages/examples/react-webpack/src/app/components/formNative/FormNative.tsx b/packages/examples/react-webpack/src/app/components/formNative/FormNative.tsx index 7811fd78e..1913d844b 100644 --- a/packages/examples/react-webpack/src/app/components/formNative/FormNative.tsx +++ b/packages/examples/react-webpack/src/app/components/formNative/FormNative.tsx @@ -40,11 +40,9 @@ function FormNative(): ReactElement {

Current configuration:
- - All fields required: {areAllRequired.toString()} + - All fields required: { areAllRequired.toString() }

- {/* KO no odsChange event on mount */} - {/* KO no odsChangeEvent on reset */}
Goldfish - {/* KO required not reactive */}