Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Examples of react hook form #88

Merged

Conversation

AkifumiSato
Copy link
Collaborator

I wanted a performance-optimized implementation, but could not come up with a way to control the number of times rendering was called.
I have tried the following.

  • With snapshot -> Rendering call increases.
  • Controlling by defaultValue of terminal component -> increases drawing.

If there is a better way, I need advice.

@koichik
Copy link
Member

koichik commented Sep 4, 2022

My PoC is here:

  • The wrapper for useForm()
type UseFormSyncReturn<
  TFieldValues extends FieldValues = FieldValues,
  TContext = any
> = UseFormReturn<TFieldValues, TContext> & { onChangeForm: () => void }

function useFormSync<
  TFieldValues extends FieldValues = FieldValues,
  TContext = any
>(
  formState: RecoilState<TFieldValues>,
  props?: Omit<UseFormProps<TFieldValues, TContext>, 'defaultValues'>
): UseFormSyncReturn<TFieldValues> {
  const getDefaultValues = useRecoilCallback(
    ({ snapshot }) =>
      () => {
        return snapshot.getLoadable(formState).contents
      },
    []
  )
  const defaultValuesRef = useRef<DeepPartial<TFieldValues>>()
  defaultValuesRef.current ??= getDefaultValues()

  const {
    getValues,
    reset: resetForm,
    ...rest
  } = useForm<TFieldValues, TContext>({
    ...props,
    defaultValues: defaultValuesRef.current,
  })

  const setFormValues = useSetRecoilState(formState)
  const onChangeForm = useCallback(() => {
    setFormValues(getValues())
  }, [setFormValues, getValues])

  const resetState = useResetRecoilState(formState)
  const reset = useCallback(() => {
    resetState()
    resetForm(getDefaultValues())
  }, [getDefaultValues, resetForm, resetState])

  return { ...rest, getValues, reset, onChangeForm }
}

EDIT: support for reset.

  • Page Component:
type FormState = {
  name: string
  comment: string
}

const formState = initializableAtom<FormState>({
  key: 'formState',
  effects: [
    syncEffect({
      refine: object({
        name: string(),
        comment: string(),
      }),
    }),
  ],
})

const FormPage: NextPage = () => {
  const { register, onChangeForm } = useFormSync(
    formState({ name: 'a', comment: 'b' })
  )

  return (
    <>
      <form onChange={onChangeForm}>
        <input type="text" {...register('name')} />
        <input type="text" {...register('comment')} />
      </form>
    </>
  )
}
export default FormPage

The key points to avoid re-rendering are:

  • use useRecoilCallback() to get the snapshot.
  • use useSetRecoilState() to update the state.

Probably we need to improve on this, but I think this is a good starting point.

@AkifumiSato
Copy link
Collaborator Author

AkifumiSato commented Sep 4, 2022

@koichik It was a lack of understanding of useRecoilCallback.
Thank you, I understand much better.

  • Reflects the proposed code: b16fdef
  • A little refactor in my own way: 0d9c56e

Copy link
Member

@koichik koichik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits, but LGTM. Nice work!

@AkifumiSato AkifumiSato merged commit ecadac8 into recruit-tech:main Sep 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants