Home > front end >  Dispatch API response with Redux and Typescript
Dispatch API response with Redux and Typescript

Time:01-19

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;
     })
});
  • Related