Search code examples
reactjstypescriptreact-nativereduxredux-thunk

How to dispatch an AsyncThunk with the createAsyncThunk helper


I am currently trying to learn some typescript/redux/reactnative.

I think I have gotten the basic concepts of how redux handles state management. I am now however a bit stuck on trying to get asynchronous state management to work with the redux thunk middleware.

So far I have got this simple counter example:

Rootreducer.tsx

import { combineReducers } from "@reduxjs/toolkit";
import { create } from "react-test-renderer";
import { createStore, applyMiddleware } from "redux"
import thunk, {ThunkMiddleware} from "redux-thunk";
import { AppActions } from "../Util/Types";
import counterReducer from "./CounterReducer"
export const rootReducer = combineReducers({
    counter: counterReducer
})
export type AppState = ReturnType<typeof rootReducer>

const middleware = applyMiddleware(thunk as ThunkMiddleware<AppState, AppActions>)
export const store = createStore(rootReducer, middleware)

CounterReducer.tsx:

import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit"
import { CounterState } from "../Util/Types"

const initialState = { num : 0 } as CounterState

function delay(milliseconds: number, count: number): Promise<number> {
    return new Promise<number>(resolve => {
            setTimeout(() => {
                resolve(count);
            }, milliseconds);
        });
    }

export const delayedIncrement = 
    createAsyncThunk (
        "delayedIncrement",
        async(arg : number , thunkAPI) => {
            const response = await delay(5000, 5)
            return response
        }
    )

const counterSlice = createSlice({
    name: "counter",
    initialState,
    reducers: {
        increment(state) {
            state.num++
        },
        decrement(state) {
            state.num--
        },
        incrementByAmount (state, action : PayloadAction<number>) { 
            state.num += action.payload
        }
    },
    extraReducers : {
        [delayedIncrement.fulfilled.type]: (state, action) => {
            state.num += action.payload
        },
        [delayedIncrement.pending.type]: (state, action) => {
            state.num
        }
    }
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

Counter.tsx:

import { FC, useState } from "react";
import  { Button, Text, View } from "react-native"
import React from "react"
import { connect, ConnectedProps, useDispatch } from "react-redux"
import  { AppState } from "../Reducers/RootReducer" 
import { increment, decrement, incrementByAmount, delayedIncrement} from "../Reducers/CounterReducer";

const mapState = (state : AppState) => ({
    counter: state.counter.num
})
const mapDispatch = {
    increment : () => ({ type: increment }),
    decrement : () => ({ type : decrement }),
    incrementByAmount : (value : number) => ({type: incrementByAmount, payload: 5})
}
const connector = connect(
    mapState,
    mapDispatch
)
type PropsFromRedux = ConnectedProps<typeof connector>
interface CounterProps extends PropsFromRedux  {
    a : string
}
const Counter : FC<CounterProps> = (props) => {
    const dispatch = useDispatch 
    return (
        <View>
            <Text>{props.a}</Text>
            <Text>{props.counter.toString()}</Text>
            <Button title="increment" onPress={props.increment}> </Button>
            <Button title="decrement" onPress={props.decrement}> </Button>
            <Button title="increment5" onPress= {  () => { props.incrementByAmount(5) }}> </Button>
            <Button title="delayed" onPress= {  () => { dispatch (delayedIncrement(5)) }}> </Button>

        </View>
    )
}
export default connector(Counter)

When I try to dispatch the delayed increment as such:

<Button title="delayed" onPress= {  () => { dispatch (delayedIncrement(5)) }}> </Button>

I get the following error:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

I have followed the documentation provided by redux fairly closely so I am not sure why I cannot get this to function? The error itself does not make much sense to me, but I am not that familiar with javascript in general.

Any pointers or help of any kind would be much appreciated.


Solution

  • Your React component is wrong.

    You have:

    const dispatch = useDispatch
    

    Notice that this just assigns the useDispatch hook itself as the dispatch function, which is incorrect. That means that later on, when you call dispatch(), you're actually calling useDispatch() instead. That leads to the hooks usage error.

    Instead, you need:

    const dispatch = useDispatch();
    

    ie, call the hook now, save the result as a variable named dispatch.