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.