From 70ff80c17541ca445610e020a6f9b402e0c648d9 Mon Sep 17 00:00:00 2001 From: Dennis Nikolay Date: Thu, 19 Dec 2024 12:41:31 +0100 Subject: [PATCH] [2.x] Fix useForm re-renders by memoizing functions in React (same changes as inertiajs#1607 for v2) --- packages/react/src/useForm.ts | 110 +++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 41 deletions(-) diff --git a/packages/react/src/useForm.ts b/packages/react/src/useForm.ts index cdf449744..15ce6af64 100644 --- a/packages/react/src/useForm.ts +++ b/packages/react/src/useForm.ts @@ -59,7 +59,7 @@ export default function useForm( const [progress, setProgress] = useState(null) const [wasSuccessful, setWasSuccessful] = useState(false) const [recentlySuccessful, setRecentlySuccessful] = useState(false) - let transform = (data) => data + const transform = useRef((data) => data) useEffect(() => { isMounted.current = true @@ -158,17 +158,16 @@ export default function useForm( } if (method === 'delete') { - router.delete(url, { ..._options, data: transform(data) }) + router.delete(url, { ..._options, data: transform.current(data) }) } else { - router[method](url, transform(data), _options) + router[method](url, transform.current(data), _options) } }, [data, setErrors, transform], ) - return { - data, - setData(keyOrData: keyof TForm | Function | TForm, maybeValue?: TForm[keyof TForm]) { + const setDataFunction = useCallback( + (keyOrData: keyof TForm | Function | TForm, maybeValue?: TForm[keyof TForm]) => { if (typeof keyOrData === 'string') { setData((data) => ({ ...data, [keyOrData]: maybeValue })) } else if (typeof keyOrData === 'function') { @@ -177,17 +176,11 @@ export default function useForm( setData(keyOrData as TForm) } }, - isDirty: !isEqual(data, defaults), - errors, - hasErrors, - processing, - progress, - wasSuccessful, - recentlySuccessful, - transform(callback) { - transform = callback - }, - setDefaults(fieldOrFields?: keyof TForm | Partial, maybeValue?: FormDataConvertible) { + [setData], + ) + + const setDefaultsFunction = useCallback( + (fieldOrFields?: keyof TForm | Partial, maybeValue?: FormDataConvertible) => { if (typeof fieldOrFields === 'undefined') { setDefaults(() => data) } else { @@ -197,11 +190,15 @@ export default function useForm( })) } }, - reset(...fields) { + [data, setDefaults], + ) + + const reset = useCallback( + (...fields) => { if (fields.length === 0) { setData(defaults) } else { - setData( + setData((data) => (Object.keys(defaults) as Array) .filter((key) => fields.includes(key)) .reduce( @@ -214,7 +211,11 @@ export default function useForm( ) } }, - setError(fieldOrFields: keyof TForm | Record, maybeValue?: string) { + [setData, defaults], + ) + + const setError = useCallback( + (fieldOrFields: keyof TForm | Record, maybeValue?: string) => { setErrors((errors) => { const newErrors = { ...errors, @@ -226,7 +227,11 @@ export default function useForm( return newErrors }) }, - clearErrors(...fields) { + [setErrors, setHasErrors], + ) + + const clearErrors = useCallback( + (...fields) => { setErrors((errors) => { const newErrors = (Object.keys(errors) as Array).reduce( (carry, field) => ({ @@ -239,26 +244,49 @@ export default function useForm( return newErrors }) }, + [setErrors, setHasErrors], + ) + + const createSubmitMethod = (method) => (url, options) => { + submit(method, url, options) + } + const get = useCallback(createSubmitMethod('get'), [submit]) + const post = useCallback(createSubmitMethod('post'), [submit]) + const put = useCallback(createSubmitMethod('put'), [submit]) + const patch = useCallback(createSubmitMethod('patch'), [submit]) + const deleteMethod = useCallback(createSubmitMethod('delete'), [submit]) + + const cancel = useCallback(() => { + if (cancelToken.current) { + cancelToken.current.cancel() + } + }, []) + + const transformFunction = useCallback((callback) => { + transform.current = callback + }, []) + + return { + data, + setData: setDataFunction, + isDirty: !isEqual(data, defaults), + errors, + hasErrors, + processing, + progress, + wasSuccessful, + recentlySuccessful, + transform: transformFunction, + setDefaults: setDefaultsFunction, + reset, + setError, + clearErrors, submit, - get(url, options) { - submit('get', url, options) - }, - post(url, options) { - submit('post', url, options) - }, - put(url, options) { - submit('put', url, options) - }, - patch(url, options) { - submit('patch', url, options) - }, - delete(url, options) { - submit('delete', url, options) - }, - cancel() { - if (cancelToken.current) { - cancelToken.current.cancel() - } - }, + get, + post, + put, + patch, + delete: deleteMethod, + cancel, } }