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
feat: improve useAsyncFn and useAsync typings #589
Conversation
Now it uses fully generic arguments and returning types, thus no problems with inferring callback call arguments;
Fix function name;
aac8866
to
563f71d
Compare
Hi, I think these types have been made a bit too complex, I'll expand on this tomorrow, just leaving this here to flag the merging of it |
@fabiancook any results on making it simpler? |
My main concern is that an unneeded type is being used. Instead of export type AsyncFnReturn<T extends FnReturningPromise = FnReturningPromise> = [AsyncState<ReturnType<T>>, T]; You should have export type AsyncReturnFn<T, P extends any[]> = [AsyncState<T>, (...args: P) => Promise<T>]; This allows the parameters to be passed, along with the return type, removing the need for |
It is made this way now and has problems with returned function arguments. |
What is pending? This is cumbersome for a very common use case, form submission: const [state, submit] = useAsyncFn(async (e: React.FormEvent) => {
e.preventDefault()
// submit form
})
return (
<form onSubmit={submit} {...props}>
{children}
</form>
) Currently I must wrap it on site for const [state, submit] = useAsyncFn(async () => {
// submit form
})
const formRef = useRef<HTMLFormElement>(null)
return (
<form ref={formRef} onSubmit={async e => {
e.preventDefault()
return submit()
} {...props}>
{children}
</form>
) |
This would really be helpful if this is fixed 🙂. Below is what I'm having to do in the meantime interface ApiRequestData {}
interface ApiResponse {}
const [state, apiCall] = useAsyncFn<
AxiosResponse<ApiResponse>,
[ApiRequestData]
>((...args) => {
const data = args[0] // 😩
return axios.post('/api/call', data)
}) while what I want to do is const [state, apiCall] = useAsyncFn<
AxiosResponse<ApiResponse>,
ApiRequestData // error here
>((data: ApiRequestData) => {
return axios.post('/api/call', data)
})
/* error
Type 'ApiRequestData' does not satisfy the constraint 'any[]'.
Type 'ApiRequestData' is missing the following properties from type 'any[]': length, pop, push, concat, and 28 more.
*/ And when I try to fix that error, I get const [state, apiCall] = useAsyncFn<
AxiosResponse<ApiResponse>,
[ApiRequestData]
>((data: ApiRequestData) => { // error here
return axios.post('/api/call', data)
})
/* error
Argument of type '(data: ApiRequestData) => Promise<AxiosResponse<ApiResponse>>' is not assignable to parameter of type '(...args: [ApiRequestData] | []) => Promise<AxiosResponse<ApiResponse>>'.
Types of parameters 'data' and 'args' are incompatible.
Type '[ApiRequestData] | []' is not assignable to type '[ApiRequestData]'.
Property '0' is missing in type '[]' but required in type '[ApiRequestData]'.
*/ |
@pnarielwala-tc dows this pr solve your problem? if yes - i'll merge it. |
@xobotyi so unfortunately I ran some minor issues as I pulled your change locally into my codebase. I was also getting eslinting issues with the import { useCallback, useState } from 'react'
import { useMountedState } from 'react-use'
export type FnReturningPromise = (...args: any[]) => Promise<any>
export type PromiseType<P extends Promise<any>> = P extends Promise<infer T>
? T
: never
export type AsyncState<T> =
| {
loading: boolean
error?: undefined
value?: undefined
}
| {
loading: false
error: Error
value?: undefined
}
| {
loading: false
error?: undefined
value: T
}
type StateFromFnReturningPromise<T extends FnReturningPromise> = AsyncState<
PromiseType<ReturnType<T>>
>
export type AsyncFnReturn<T extends FnReturningPromise = FnReturningPromise> = [
StateFromFnReturningPromise<T>,
T,
]
export default function useAsyncFn<T extends FnReturningPromise>(
fn: T,
initialState: StateFromFnReturningPromise<T> = { loading: false },
): AsyncFnReturn<T> {
const isMounted = useMountedState()
const [state, set] = useState<StateFromFnReturningPromise<T>>(initialState)
const callback = useCallback(
(...args: Parameters<T>): ReturnType<T> => {
isMounted() && set({ loading: true }) // for when the component using this hook unmounts for some reason
return fn(...args).then(
value => {
isMounted() && set({ value, loading: false })
return value
},
error => {
isMounted() && set({ error, loading: false })
return error
},
) as ReturnType<T>
},
[fn, isMounted], // replaced `deps` here as I didn't have a real use case for a dependency list
// also when this was just `deps` (as React.DependecyList), I was getting warnings listed below
)
return [state, (callback as unknown) as T]
} eslint warnings
|
95c7eb3
to
361af9a
Compare
4baf4c2
to
7e57723
Compare
What is pending? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
I am also struggling with this problem, when will v14.0 release? Thanks! |
Thanks a lot for that PR @xobotyi, thanks to you I'm gonna remove most of the ts-ignore of my codebase :) |
🎉 This PR is included in version 15.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
useAsyncFn and useAsync typings made great again =)
Now it uses fully generic arguments and returning types, thus no problems with inferring callback arguments and value types;
PS: @streamich check the #381 PR too.