Home > Net >  How to save the result of my API call to my redux store, and call my API when my component firsts lo
How to save the result of my API call to my redux store, and call my API when my component firsts lo

Time:11-24

So I created a react redux toolkit app and I am trying to play with the redux and actions.

I have a simple react component that looks like this:

import React, { useState } from 'react';

import { useAppSelector, useAppDispatch } from '../../app/hooks';
import {
  setBoard,
  fetchBoardAsync,
  selectBoard
} from './boardSlice';
import styles from './Board.module.css';

export function Board() {
  const board = useAppSelector(selectBoard);
  const dispatch = useAppDispatch();

  console.log(JSON.stringify(board));

  return (
    <div>
      <div className={styles.row}>
        <h3>Board</h3>
       
        <button
          className={styles.asyncButton}
          onClick={() => dispatch(fetchBoardAsync(""))}
        >
          Fetch board Data from API
        </button>
      </div>
    </div>
  );
}

When I click the button, it fetches some JSON - but right now it is just hard coded json variable.

// A mock function to mimic making an async request for data
export function fetchBoard() {
    return new Promise<{ data: any }>((resolve) =>
      setTimeout(() => resolve({ data: boardJson} ), 50)
    );
  }

where boardJson is simply:

let boardJson = { .... }

My boardSlice looks like:

import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState, AppThunk } from '../../app/store';
import { fetchBoard } from './boardAPI';

export interface BoardState {
  board: any;
  status: 'idle' | 'loading' | 'failed';
}

const initialState: BoardState = {
  board: {},
  status: 'idle',
};

export const fetchBoardAsync = createAsyncThunk(
  'board/fetchBoard',
  async (boardId: string) => {
    const response = await fetchBoard();
    console.log('fetch board returned data...'   JSON.stringify(response.data));
    return response.data;
  }
);

export const boardSlice = createSlice({
  name: 'board',
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: {
      setBoard: (state, action: PayloadAction<any>) => {
          state.board = action.payload;
      }
  },

I have 2 questions.

#1

When I click the button and it fetches the board, how do I save the result to my redux state?

#2 Also, in my Board component, how can I run fetchBoard when the component loads instead of having to click a button to trigger it? (this used to be a component lifecycle event like componentDidMount I think)

CodePudding user response:

When I click the button and it fetches the board, how do I save the result to my redux state?

Your boardSlice reducer almost does what you expect. In the reducers field, you have provided an object of key/reducer values. Redux is unable to associate the key setBoard with the action created by fetchBoardAsync. If you were to dispatch({ type: 'setBoard', payload }) then you would see your reducer fire as expected. Notice how the type is identical to the reducer key.

createAsyncThunk example from Redux documentation

Matching utilities in RTK

Instead, you can use RTK builder syntax and matchers for the desired result:

export const boardSlice = createSlice({
  name: 'board',
  initialState,
  extraReducers(builder) {
    builder.addCase(fetchBoardAsync.fulfilled, (state, action) => {
      state.board = action.payload;
    })
  },
};

#2 Also, in my Board component, how can I run fetchBoard when the component loads instead of having to click a button to trigger it? (this used to be a component lifecycle event like componentDidMount I think)

In a functional component, most React developers use the React hook useEffect for managing side-effects in a similar fashion to componentDidMount/componentWillMount and componentWillUnmount. The documentation provides a lot of clarity on all the ways developers can leverage this hook.

For example:

import React, { useEffect, useState } from 'react';

import { useAppSelector, useAppDispatch } from '../../app/hooks';
import {
  setBoard,
  fetchBoardAsync,
  selectBoard
} from './boardSlice';
import styles from './Board.module.css';

export function Board() {
  const board = useAppSelector(selectBoard);
  const dispatch = useAppDispatch();
  useEffect(() => {
    dispatch(fetchBoardAsync(""))
  }, []); // an empty dependency array tells React to only run the effect after mount

  console.log(JSON.stringify(board)); // may not have loaded yet

  return (
    <div>
      {/* snip */}
    </div>
  );
}

Once dispatched, thunks receive a second parameter for additional Redux functionality. A traditional thunk, which is the result of dispatching a function, receives redux API with this signature:

function fetchBoardAsync() {
  return (dispatch, getState) => {
    // now you can dispatch even more actions!
    // I don't believe this is necessary for your current use-case, but this is helpful for complicated side-effects in many apps
  }
}

Redux thunk documentation

When using createAsyncThunk, the signature is this:

function fetchBoardAsync(arg, api) {
  const { dispatch, extra, fulfillWithValue, getState, rejectWithValue, requestId, signal } = api;
}

createAsyncThunk signature

Remember, you must still dispatch the thunk (either a function or the return value of createAsyncThunk) itself before you can use dispatch inside the thunk callback.

  • Related