Im very new to Typescript and I'm trying to use Redux with it and I'm completely lost and cannot seem to understand Redux doc about it.
I'm trying to fetch data and dispatch them to my reducer to be able to use them afterward like I always did before using Typescript, but now my way does not work like this:
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import axios from 'axios';
import { useAppDispatch } from './hooks';
export interface MealState {
meal:string[],
input: string,
favorites: string[],
categories: string[],
}
const initialState: MealState = {
meal:[],
input: '',
favorites: [],
categories: [],
};
const mealReducer = createSlice({
name: 'meal',
initialState,
reducers: {
// GET ALL MEAL
getAllMeal: (state, action: PayloadAction<string[]>) => {
console.log('Do something') //<== trying to at least do this
state.meal = action.payload;
},
// ADD INPUT
addInput: (state, action: PayloadAction<string>)=> {
state.input = action.payload
},
}
});
// ----------------------------------------------------------------------
export default mealReducer.reducer
// Actions
export const {
addInput,
} = mealReducer.actions;
export const getMeal = createAsyncThunk(
'meal/getMeal',
async (search: string) => {
const response: string[] = await axios.get(`https://www.themealdb.com/api/json/v1/1/search.php?s=${search}`);
console.log(response);
const dispatch = useAppDispatch();
dispatch(mealReducer.actions.getAllMeal(response));
}
);
Here is my code to trigger it (I do get the api response, I just can't dispatch it)
import { useEffect, useState } from 'react';
import { useAppDispatch, useAppSelector } from '../redux/hooks';
import { getMeal, addInput } from '../redux/meal';
import { RootState } from '../redux/store';
import {getCategories} from '../api/api'
const Header = () => {
const dispatch = useAppDispatch();
const input = useAppSelector((state: RootState) => state.meal.input)
const [inputText, setInputText] = useState(input);
const inputHandler = (e: { target: { value: string; }; }) => {
setInputText(e.target.value.toLowerCase());
};
useEffect(() => {
dispatch(getMeal(inputText));
dispatch(addInput(inputText))
}, [dispatch, inputText]);
return (
<Box sx={{ m: 3 }}>
<TextField
onChange={inputHandler}
placeholder="Search"
sx={{ mb: 3 }}
/>
</Box>
);
}
export default Header
CodePudding user response:
Your issues here have nothing to do with TypeScript. The problem is your thunk. Specifically, the useAppDispatch()
call.
export const getMeal = createAsyncThunk(
'meal/getMeal',
async (search: string) => {
const response: string[] = await axios.get(`https://www.themealdb.com/api/json/v1/1/search.php?s=${search}`);
console.log(response);
>> const dispatch = useAppDispatch();
dispatch(mealReducer.actions.getAllMeal(response));
}
);
You cannot use React hooks inside of thunk action creator. You can only call hooks inside React function components or other hooks. (Docs: Rules of Hooks)
You don't need actually need to access dispatch
here. But if you did need to use dispatch
inside of createAsyncThunk
, you would access it through the arguments of your payload creator function.
export const getMeal = createAsyncThunk(
'meal/getMeal',
async (search: string, { dispatch }) => {
const response: string[] = await axios.get(`https://www.themealdb.com/api/json/v1/1/search.php?s=${search}`);
console.log(response);
dispatch(mealReducer.actions.getAllMeal(response));
}
);
But you shouldn't do that because you would be missing the point of createAsyncThunk
. A createAsyncThunk
action already dispatches a fulfilled
action when it is complete. You should return
the response data in your thunk to set the payload of this automatic action. Remove the getAllMeal
case reducer and and instead respond to the getMeal.fulfilled
action through extraReducers
.
export const getMeal = createAsyncThunk(
'meal/getMeal',
async (search: string) => {
const response = await axios.get<string[]>(`https://www.themealdb.com/api/json/v1/1/search.php?s=${search}`);
return response.data;
}
);
const mealReducer = createSlice({
name: 'meal',
initialState,
reducers: {
// ADD INPUT
addInput: (state, action: PayloadAction<string>)=> {
state.input = action.payload
},
},
extraReducers: builder => builder
.addCase(getMeal.fulfilled, (state, action) => {
console.log('Do something') //<== trying to at least do this
state.meal = action.payload;
})
});