reduxjs / redux-essentials-example-app

Example app for the Redux Essentials tutorial
https://redux.js.org/tutorials/essentials/part-1-overview-concepts
304 stars 811 forks source link

Property 'unwrap' does not exist on type 'AsyncThunkAction<any, void, {}>'. #40

Closed eRuaro closed 2 years ago

eRuaro commented 2 years ago

I'm trying to get the AddPostForm component to work but I'm running into an error on the onSavePostClicked function. I'm currently on part 5 of the redux essentials tutorial.

Here's a snippet of AddPostForm which contains the onSavePostClicked function:

export const AddPostForm = () => {
    // omit useState and onChanged functions
    const dispatch = useDispatch();
    const users = useSelector((state: RootState) => state.users.users)

    const canSave = [title, content, userId].every(Boolean) && addRequestStatus === "idle";

    const onSavePostClicked = async () => {
        if (canSave) {
            try {
                setAddRequestStatus('pending')
                await dispatch(addNewPost({ title, content, user: userId })).unwrap()
                setTitle('')
                setContent('')
                setUserId('')
            } catch (err) {
                console.error('Failed to save the post: ', err)
            } finally {
                setAddRequestStatus('idle')
            }
        }
    }

    // omit return value 
}

There's specifically an error on this line

await dispatch(addNewPost({ title, content, user: userId })).unwrap()

The errors are:

  1. Expected 0 arguments, but got 1.
  2. Property 'unwrap' does not exist on type 'AsyncThunkAction<any, void, {}>'.

Here's the definition of addNewPost:

export const addNewPost = createAsyncThunk('posts/addNewPost', async initialPost => {
    // send initial date to the fake api server
    const response = await client.post('/fakeApi/posts', initialPost)
    // the response includes the complete post object, including unique id
    return response.data
})

I followed through the tutorial and didn't see any difference in my code. The only difference is that I was replicating things through Typescript. Would love it if you guys can guide me in the right direction for solving this.

markerikson commented 2 years ago

Yeah, unfortunately you're trying to do it using TypeScript, and the current tutorial content only teaches plain JS. So, you're missing a couple key pieces of info atm.

The first issue is that the base Redux Dispatch type doesn't know about thunks, and so it doesn't know that dispatching a thunk can return a promise, or the special promise with a .unwrap() method returned by createAsyncThunk.

If you follow the setup instructions we have at https://redux-toolkit.js.org/tutorials/typescript, infer type AppDispatch = typeof store.dispatch, and use that enhanced AppDispatch type with the useAppDispatch hook, then TS will know that you can do dispatch(someAsyncThunk()).unwrap().

The other problem is that createAsyncThunk needs to know what the correct type is for its argument. The simplest way to do that is:

createAsyncThunk('posts/addNewPost', async (initialPost: SomePostTypeHere) => {

});

FYI I'm now planning to try adding a new page to the end of the "Essentials" tutorial that would help cover how to do all the same code with TS, specifically to help explain things like this. No ETA on when I might have that ready.

You can also look at the Redux+TS CRA template for an example setup:

https://github.com/reduxjs/cra-template-redux-typescript/tree/master/template/src

eRuaro commented 2 years ago

Awesome, thanks! I've actually been following along with the tutorial, and using the redux typescript template as my go-to resource for translating JS code to TS.

markerikson commented 2 years ago

Yeah, I was actually asking on Twitter over the last couple days what the best approach would be for integrating TS concepts into the "Essentials" tutorial.

There were a bunch of suggestions to rewrite the entire tutorial in TS. But, a lot of folks are learning Redux right after getting into JS and React, and I'm afraid a TS-first tutorial would make it a lot harder for people to learn. So, my intent is to add a new page at the end that shows "hey, here's how to do most of the stuff you just did, but with TS", and maybe add some breadcrumbs / links from the earlier pages to the last one that say "if you want to know how to do this in TS, see this part of the final page". Hopefully that'll be a reasonable balance.