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
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
}
}
When using createAsyncThunk
, the signature is this:
function fetchBoardAsync(arg, api) {
const { dispatch, extra, fulfillWithValue, getState, rejectWithValue, requestId, signal } = api;
}
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.