I am trying to make an api call with an optional parameter however typescript complains
export const usersLoad = createAsyncThunk(
'users/load',
(filters: UserFilters = {}) => Api.get<User[]>('/api/users/search', filters)
);
...
dispatch(usersLoad());
Expected 1 arguments, but got 0
I have a similar issue with this action
export const usersLoadMore = createAsyncThunk(
'users/loadMore',
(_: SyntheticEvent | void, thunkApi) => {
const state = thunkApi.getState() as RootState;
const { filters, data } = state.users;
return Api.get<User[]>('/api/users/search', {
...filters,
offset: data.length,
});
}
);
here I don't use the arg but I had to give it the SyntheticEvent or it complained when passing it to onClick, and I had to give it void to make it work when I call it directly. I would think that I would not have to set it to anything since I am not using it.
Yeah, that's not possible at the moment.
I will look into this in the near future, but in the meantime you can do something like
const _usersLoad = createAsyncThunk(
'users/load',
(filters: UserFilters) => Api.get<User[]>('/api/users/search', filters)
);
export const usersLoad = (filters: UserFilters = {}) => _usersLoad(filters);
I admit it's a bit hacky, but it should do as a workaround for now.
ya i'm working around it.
still new to typescript but it looks like the typing returns a function with the signature
(arg: ThunkArg) => .... making arg required
Yes. As I said:
that's not possible at the moment.
I will look into this in the near future
We simply didn't anticipate this use case and none of our beta testers had it either, so we'll have to add it.
Should be resolved by https://github.com/reduxjs/redux-toolkit/releases/tag/v1.3.5 .
@markerikson please could you show an example of an optional argument? That like looks like it just cancels it no? Also it seem using createAsyncThunk seems to be more boilerplate in components? I seem to have:
// someThunk.ts
export const fetchSomeThunk = createAsyncThunk(
'app/fetchSomeThunk',
async (arg: IMyArg) => {
return axios.get(url)
}
)
and then in a component:
// myComponent.tsx
const someThunkCall = async () => {
try {
const someThunkAction = await dispatch(someThunk(''))
unwrapResult(someThunkAction)
} catch (error) {
Bugsnag.notify(error)
}
}
where as I quite liked:
// someThunkOld.tsx
export const someOldSchoolActionCreator = (): Thunk => async (dispatch) => {
try {
const resp = await axios.get(url)
dispatch(someThunkSuccess(resp.data))
} catch (err) {
console.error(err)
}
}
as it's all together in one place, limited duplication. It is nice having someThunkCall as you can directly update state but it's a bit of a weird uni-directional flow, a bit back and forth? What's the implications, if any, of the someOldSchoolActionCreator way?
Uh... sorry, I'm really not sure what you're asking there, in either part of that comment. Can you clarify what you're asking?
You mentioned: Should be resolved by https://github.com/reduxjs/redux-toolkit/releases/tag/v1.3.5 . but I don't really see how that example relates to the title issue of: optional or no args. Just wondered if you can show an example of a createAsyncThunk with "option or no args" in it?
And what's the benefit of using fetchSomeThunk in comparison to someOldSchoolActionCreator??
I would assume that the very first comment in this thread is the "optional arg" that's being discussed:
export const usersLoad = createAsyncThunk(
'users/load',
(filters: UserFilters = {}) => Api.get<User[]>('/api/users/search', filters)
);
Here, the one argument to the payload creator is filters: UserFilters, but since a default value has been provided, TS makes that optional: filters?: UserFilters.
That means it _should_ be legal to dispatch this thunk either _with_ a parameter passed in:
dispatch(usersLoad(someFiltersValue));
or _without_ any parameters:
dispatch(usersLoad());
The issue was that the original typings in 1.3.4 and earlier did not allow the second option to work correctly with TypeScript, and so it would yell at you if you didn't provide a value. As of 1.3.5, dispatch(usersLoad()) _should_ work okay with the TS compiler.
I'm still not entirely sure what you're asking about using createAsyncThunk vs writing a thunk by hand. Overall, the point of createAsyncThunk is to abstract the standard "dispatch actions based on a promise" pattern. If you need to dispatch actions in that pattern, createAsyncThunk is there to simplify it for you. If you don't need that pattern, then don't use createAsyncThunk.
I'll note that your couple thunk examples there are doing different things, so I'm not sure why you're trying to compare them as if they're identical.
Thanks for the explanation, good clarification. Would there be a nice way to reuse someThunkCall? As what happens if I want to use it in multiple components?
Sorry, really having trouble understanding what you're trying to describe here :( Can you give specific examples?
Ok no worries, so let's say I had an async thunk like this:
// app/thunks
export const fetchSomeThunk = createAsyncThunk(
'app/fetchSomeThunk',
async (arg: IMyArg) => {
return axios.get(url)
}
)
and I wanted to use/call it in two components: ComponentA and ComponentB
// ComponentA.tsx and ComponentB.tsx
const someThunkCall = async () => {
try {
const someThunkAction = await dispatch(someThunk(''))
unwrapResult(someThunkAction)
} catch (error) {
Bugsnag.notify(error)
}
}
so I don't have to duplicate someThunkCall in both components, would I simply pass dispatch in to someThunkCall as an argument?
I guess? This is ultimately _you_ creating your own abstraction for additional logic in a component after dispatching.
Hmm, this feel extremely standard to me. Have you've tried to build a real-world app with toolkit? (don't mean that in a rude way) but calling action creators from different components seems quite standard, or maybe I've been doing it wrong since working with redux (which there is a high chance)
Yes, I've worked on some "real" apps. I've just never needed to spend much time checking thunk results in components, or sending off error logs to bug reporting services, etc.
My point is that what you're asking about here is really about _your_ own abstractions, in _your_ app. This isn't anything that is a bug, or requires API changes, or anything like that. It's your app, I don't know what your requirements are, and I don't know what use cases you're trying to solve. You're always welcome to come up with your own abstractions and use them. So, I'm not sure what advice you're looking for from me atm.
That's ok, just trying to understand how to use it better in future 馃憤 no bug, thanks for the help; really appreciate it. P.s sorry for the "real" world comment, I didn't it to come across so rudely.
Adding to this, your example above could also be written:
// myComponent.tsx
const someThunkCall = async () => {
try {
- const someThunkAction = await dispatch(someThunk(''))
- unwrapResult(someThunkAction)
+ dispatch(someThunk('')).then(unwrapResult)
} catch (error) {
Bugsnag.notify(error)
}
}
in the next version of RTK we are probably adding a helper so it can also be written as
// myComponent.tsx
const someThunkCall = async () => {
try {
- dispatch(someThunk('')).then(unwrapResult)
+ dispatch(someThunk('')).unwrap()
} catch (error) {
Bugsnag.notify(error)
}
}
which is even shorter
But in the end, it is your own decision to even do all this handling in your component. You could also just have a middleware:
const BugsnagMiddleware = api => next => action => {
if (isRejectedWithValue(action)) {
Bugsnag.notify(action.payload)
} else if (isRejected(action)) {
Bugsnag.notify(action.error)
}
return next(action)
}
From that point on all your components would become
// myComponent.tsx
const someThunkCall = () => dispatch(someThunk(''))
Doing this in the component is your decision in the first place and yes that will always mean more code.
Sorry @markerikson two further questions.
createAsyncThunk:export const usersLoad = createAsyncThunk(
'users/load',
(filters: UserFilters = {}, { getState }) => {
const state = getState()
return Api.get<User[]>('/api/users/search', {
headers: {
Authorization: `Bearer ${state.auth.accessToken}`,
},
})
}
)
usersLoad doesn't take a filter arg but you still wanted to use getState how do you do this? Would you just pass the part of the state you wanted as the first arg? As currently I get a Expected 0 arguments, but got 1 when I pass in undefined or null as the first arg.@drydenwilliams this issue really isn't a good place to provide support :( Please come by the #redux channel in the Reactiflux Discord if you have further questions.
I've always found this issues a bit more helpful as this was the first result on Google, so I hope it can help others if they find it due to the history? Hope that's ok; I feel like you're support is awesome!
Thanks for your points @phryneas I like the idea of the middleware 馃憤
This should not be the first result on google if searching for that question, because it should never have been asked here - and by now is pretty much completely unrelated to the initial issue discussed in this bug tracker.
Instead, a StackOverflow thread with a useful title and a specific problem & solution should be the first google hit.
Which is only possible if that question is asked & answered over there instead of here.
Not sure these points are "pretty much completely unrelated" as I can't seem to understand how to use "createAsyncThunk optional or no args" which is the issue title?
Sure that might be in the wrong place now and possibly "should never have been asked here" but will be helpful now to other having a similar issue now?
They are completely unrelated to the question if there is a way to define an asyncThunk without args or with optional args.
The only thing they have in common is that they are about createAsyncThunk.
Most helpful comment
Yes. As I said:
We simply didn't anticipate this use case and none of our beta testers had it either, so we'll have to add it.