Skip to content

Commit

Permalink
[1.x] Fix useForm re-renders by memoizing functions in React (#1607)
Browse files Browse the repository at this point in the history
* fix react's useForm hook that returns new references for functions like setData, clearErrors, etc on every re-render

* fix setData and reset methods

* Fix the transform function's bug
  • Loading branch information
Hasan-Mir committed Sep 18, 2024
1 parent 91deb73 commit 0a58bc4
Showing 1 changed file with 69 additions and 41 deletions.
110 changes: 69 additions & 41 deletions packages/react/src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default function useForm<TForm extends FormDataType>(
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
Expand Down Expand Up @@ -158,17 +158,16 @@ export default function useForm<TForm extends FormDataType>(
}

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],
)

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') {
Expand All @@ -177,17 +176,11 @@ export default function useForm<TForm extends FormDataType>(
setData(keyOrData as TForm)
}
},
isDirty: !isEqual(data, defaults),
errors,
hasErrors,
processing,
progress,
wasSuccessful,
recentlySuccessful,
transform(callback) {
transform = callback
},
setDefaults(fieldOrFields?: keyof TForm | Partial<TForm>, maybeValue?: FormDataConvertible) {
[setData],
)

const setDefaultsFunction = useCallback(
(fieldOrFields?: keyof TForm | Partial<TForm>, maybeValue?: FormDataConvertible) => {
if (typeof fieldOrFields === 'undefined') {
setDefaults(() => data)
} else {
Expand All @@ -197,11 +190,15 @@ export default function useForm<TForm extends FormDataType>(
}))
}
},
reset(...fields) {
[data, setDefaults],
)

const reset = useCallback(
(...fields) => {
if (fields.length === 0) {
setData(defaults)
} else {
setData(
setData((data) =>
(Object.keys(defaults) as Array<keyof TForm>)
.filter((key) => fields.includes(key))
.reduce(
Expand All @@ -214,7 +211,11 @@ export default function useForm<TForm extends FormDataType>(
)
}
},
setError(fieldOrFields: keyof TForm | Record<keyof TForm, string>, maybeValue?: string) {
[setData, defaults],
)

const setError = useCallback(
(fieldOrFields: keyof TForm | Record<keyof TForm, string>, maybeValue?: string) => {
setErrors((errors) => {
const newErrors = {
...errors,
Expand All @@ -226,7 +227,11 @@ export default function useForm<TForm extends FormDataType>(
return newErrors
})
},
clearErrors(...fields) {
[setErrors, setHasErrors],
)

const clearErrors = useCallback(
(...fields) => {
setErrors((errors) => {
const newErrors = (Object.keys(errors) as Array<keyof TForm>).reduce(
(carry, field) => ({
Expand All @@ -239,26 +244,49 @@ export default function useForm<TForm extends FormDataType>(
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,
}
}

0 comments on commit 0a58bc4

Please sign in to comment.