diff --git a/examples/react-hook-form/pages/form-context/[index].tsx b/examples/react-hook-form/pages/form-context/[index].tsx new file mode 100755 index 00000000..403a25c2 --- /dev/null +++ b/examples/react-hook-form/pages/form-context/[index].tsx @@ -0,0 +1,265 @@ +import { useEffect, useRef } from 'react' +import type { GetServerSideProps, NextPage } from 'next' +import Head from 'next/head' +import Router from 'next/router' +import { SubmitHandler } from 'react-hook-form/dist/types/form' +import { syncEffect } from 'recoil-sync' +import { array, object, string } from '@recoiljs/refine' +import { initializableAtomFamily } from 'recoil-sync-next' +import { Links } from '../../src/components/Links' +import { useFormSync } from '../../src/hooks/useFormSync' +import { + FormSyncProvider, + useFormSyncContext, +} from '../../src/hooks/useFormSyncContext' +import styles from '../../styles/form.module.css' + +type FormState = { + name: string + comment: string + time: string + selectedRadio: string + selectedCheckbox: readonly string[] + items: readonly { + radio: string + checkbox: string + text: string + }[] +} + +const formState = initializableAtomFamily({ + key: 'historyFormState', + effects: [ + syncEffect({ + storeKey: 'history-store', + refine: object({ + name: string(), + comment: string(), + time: string(), + selectedRadio: string(), + selectedCheckbox: array(string()), + items: array( + object({ + radio: string(), + checkbox: string(), + text: string(), + }) + ), + }), + }), + ], +}) + +type PageProps = { + index: string + defaultValues: FormState +} + +let count = 1 +const createNewItem = (): FormState['items'][number] => ({ + radio: `${count}`, + checkbox: `${count++}`, + text: '', +}) + +const HistoryFormContext: NextPage = ({ index, defaultValues }) => { + // check render + const renderRef = useRef(true) + if (renderRef.current) { + renderRef.current = false + console.log(`HistoryForm[${index}]: initial render`) + } else { + console.log(`HistoryForm[${index}]: re render`) + } + + const methods = useFormSync(formState(index, defaultValues)) + const { resetFormOnly } = methods + + /* + * Next.js dynamic routes does not unmount page components + * when only slugs are changed (just re-rendering). + * Therefore, uncontrolled input elements are not updated + * from the previous page (i.e. slug). + * To solve this problem, when slugs ('index' in this case) change, + * reset the RHF form state to reflect the Recoil state. + * Unlike reset(), resetFormOnly() doesn't reset the Recoil state. + */ + useEffect(() => { + resetFormOnly() + }, [index, resetFormOnly]) + + return ( + + + + ) +} + +export default HistoryFormContext + +const HistoryFormChild: React.FC = ({ index, defaultValues }) => { + const { + control, + registerWithDefaultValue, + registerWithDefaultChecked, + onChangeForm, + handleSubmit, + reset, + resetFormOnly, + useFieldArraySync, + } = useFormSyncContext() + const { fields, append, prepend, remove, swap, move, insert } = + useFieldArraySync({ + control, + name: 'items', + }) + + const onSubmit: SubmitHandler = async (data) => { + console.log('submit data', data) + await Router.push('/success') + } + + return ( +
+ + FormContext[{index}] + + + +
+

FormContext[{index}]

+ +
+
+
name
+
+ +
+
comment
+
+ +
+
time
+
+ +
+
items
+
+ +
    + {fields.map((field, index) => ( +
  • + {index} + + + + + + + + +
  • + ))} +
+ +
+
+
+ +
+
+ + +
+
+ +
+
+ ) +} + +export const getServerSideProps: GetServerSideProps = async ({ + params, +}) => { + console.log(`HistoryForm[${params?.index}]: executing gSSP`) + return { + props: { + index: params?.index as string, + defaultValues: { + name: 'a', + comment: 'b', + time: new Date().toLocaleTimeString(), + selectedRadio: 'A', + selectedCheckbox: ['A'], + items: [ + { + radio: 'A', + checkbox: 'A', + text: '', + }, + { + radio: 'B', + checkbox: 'B', + text: '', + }, + ], + }, + }, + } +} diff --git a/examples/react-hook-form/pages/history-form/[index].tsx b/examples/react-hook-form/pages/history-form/[index].tsx index da7b0c2b..80e0d487 100755 --- a/examples/react-hook-form/pages/history-form/[index].tsx +++ b/examples/react-hook-form/pages/history-form/[index].tsx @@ -1,12 +1,12 @@ import { useEffect, useRef } from 'react' import type { GetServerSideProps, NextPage } from 'next' import Head from 'next/head' -import Link from 'next/link' import Router from 'next/router' import { SubmitHandler } from 'react-hook-form/dist/types/form' import { syncEffect } from 'recoil-sync' import { array, object, string } from '@recoiljs/refine' import { initializableAtomFamily } from 'recoil-sync-next' +import { Links } from '../../src/components/Links' import { useFormSync } from '../../src/hooks/useFormSync' import styles from '../../styles/form.module.css' @@ -99,7 +99,7 @@ const HistoryForm: NextPage = ({ index, defaultValues }) => { const onSubmit: SubmitHandler = async (data) => { console.log('submit data', data) - await Router.push('/history-form/success') + await Router.push('/success') } return ( @@ -211,17 +211,7 @@ const HistoryForm: NextPage = ({ index, defaultValues }) => { -
-
- Form[1] -
-
- Form[2] -
-
- Form[3] -
-
+ ) diff --git a/examples/react-hook-form/pages/history-form/success.tsx b/examples/react-hook-form/pages/history-form/success.tsx deleted file mode 100755 index aae36189..00000000 --- a/examples/react-hook-form/pages/history-form/success.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Head from 'next/head' -import Link from 'next/link' -import styles from '../../styles/common.module.css' - -import type { NextPage } from 'next' - -const SubmitSuccess: NextPage = () => { - return ( -
- - submit success - - - -
-

Submit success!!!

-
-
- Form[1] -
-
- Form[2] -
-
- Form[3] -
-
-
-
- ) -} - -export default SubmitSuccess diff --git a/examples/react-hook-form/pages/index.tsx b/examples/react-hook-form/pages/index.tsx index c4414534..095ee7b3 100755 --- a/examples/react-hook-form/pages/index.tsx +++ b/examples/react-hook-form/pages/index.tsx @@ -1,9 +1,8 @@ +import type { NextPage } from 'next' import Head from 'next/head' -import Link from 'next/link' +import { Links } from '../src/components/Links' import styles from '../styles/common.module.css' -import type { NextPage } from 'next' - const Home: NextPage = () => { return (
@@ -14,30 +13,7 @@ const Home: NextPage = () => {

Top page

-

History synced form

-
-
- Form[1] -
-
- Form[2] -
-
- Form[3] -
-
-

URL synced form

-
-
- Form[1] -
-
- Form[2] -
-
- Form[3] -
-
+
) diff --git a/examples/react-hook-form/pages/url-form/success.tsx b/examples/react-hook-form/pages/success.tsx similarity index 53% rename from examples/react-hook-form/pages/url-form/success.tsx rename to examples/react-hook-form/pages/success.tsx index 2ed58a3b..563557f9 100755 --- a/examples/react-hook-form/pages/url-form/success.tsx +++ b/examples/react-hook-form/pages/success.tsx @@ -1,8 +1,7 @@ -import Head from 'next/head' -import Link from 'next/link' -import styles from '../../styles/common.module.css' - import type { NextPage } from 'next' +import Head from 'next/head' +import { Links } from '../src/components/Links' +import styles from '../styles/common.module.css' const SubmitSuccess: NextPage = () => { return ( @@ -14,17 +13,7 @@ const SubmitSuccess: NextPage = () => {

Submit success!!!

-
-
- Form[1] -
-
- Form[2] -
-
- Form[3] -
-
+
) diff --git a/examples/react-hook-form/pages/url-form/[index].tsx b/examples/react-hook-form/pages/url-form/[index].tsx index f315f8e0..4e4a176b 100755 --- a/examples/react-hook-form/pages/url-form/[index].tsx +++ b/examples/react-hook-form/pages/url-form/[index].tsx @@ -1,12 +1,12 @@ import { memo, useEffect, useRef } from 'react' import type { GetServerSideProps, NextPage } from 'next' import Head from 'next/head' -import Link from 'next/link' import Router from 'next/router' import { SubmitHandler } from 'react-hook-form/dist/types/form' import { syncEffect } from 'recoil-sync' import { array, object, string } from '@recoiljs/refine' import { initializableAtomFamily } from 'recoil-sync-next' +import { Links } from '../../src/components/Links' import { useFormSync } from '../../src/hooks/useFormSync' import styles from '../../styles/form.module.css' @@ -99,7 +99,7 @@ const URLForm: NextPage = ({ index, defaultValues }) => { const onSubmit: SubmitHandler = async (data) => { console.log('submit data', data) - await Router.push('/url-form/success') + await Router.push('/success') } return ( @@ -211,17 +211,7 @@ const URLForm: NextPage = ({ index, defaultValues }) => { -
-
- Form[1] -
-
- Form[2] -
-
- Form[3] -
-
+ ) diff --git a/examples/react-hook-form/src/components/Links.tsx b/examples/react-hook-form/src/components/Links.tsx new file mode 100644 index 00000000..379bf6ef --- /dev/null +++ b/examples/react-hook-form/src/components/Links.tsx @@ -0,0 +1,32 @@ +import Link from 'next/link' + +export const Links: React.FC = () => { + return ( + <> +

History synced form

+
+ Form[1] +   + Form[2] +   + Form[3] +
+

URL synced form

+
+ Form[1] +   + Form[2] +   + Form[3] +
+

useFormSyncContext

+
+ Form[1] +   + Form[2] +   + Form[3] +
+ + ) +} diff --git a/examples/react-hook-form/src/hooks/useFormSyncContext.tsx b/examples/react-hook-form/src/hooks/useFormSyncContext.tsx new file mode 100644 index 00000000..8b947b62 --- /dev/null +++ b/examples/react-hook-form/src/hooks/useFormSyncContext.tsx @@ -0,0 +1,23 @@ +import { FieldValues, FormProvider, useFormContext } from 'react-hook-form' +import { UseFormSyncReturn } from './useFormSync' + +export type FormSyncProviderProps< + TFieldValues extends FieldValues = FieldValues, + TContext = any +> = { + children: React.ReactNode | React.ReactNode[] +} & UseFormSyncReturn + +export function FormSyncProvider< + TFieldValues extends FieldValues, + TContext = any +>(props: FormSyncProviderProps) { + const { children, ...methods } = props + return {children} +} + +export function useFormSyncContext< + TFieldValues extends FieldValues +>(): UseFormSyncReturn { + return useFormContext() as unknown as UseFormSyncReturn +}