Chaining Thunks
This is not a great design pattern but it can be done. Your "Edit2" is close, but thunkApi is the second argument of the payload creator. The first argument is the args that you pass when you call the action creator as bar(args). If you don't need an argument then you can use a _ to denote that this variable is unused.
export const foo = createAsyncThunk(
"slice/foo",
async () => {
const res = await fetch("/");
return res.json();
}
);
export const bar = createAsyncThunk(
"slice/bar",
async (_, { dispatch }) => {
const fooAction = await dispatch(foo());
return fooAction.payload;
}
);
Calling dispatch(bar()) will dispatch the actions in this order:
- "slice/bar/pending"
- "slice/foo/pending"
- "slice/foo/fulfilled"
- "slice/bar/fulfilled"
Separate Actions
As recommended by @Marcus Melodious, it is better to have completely separate actions and handle the dispatching in your component. If bar requires some piece of data from foo (like an id or a token), then it should take that data as an argument to its action creator.
Here is an example where we fetch a post and then fetch a user profile based on the userId property of that post.
export const fetchPost = createAsyncThunk(
"slice/fetchPost",
async (postId) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${postId}`
);
return res.json();
}
);
export const fetchUser = createAsyncThunk(
"slice/fetchUser",
async (userId) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
return res.json();
}
);
You can use one useEffect and look at the results from the dispatch
useEffect(() => {
const execute = async () => {
const postAction = await dispatch(fetchPost(POST_ID));
const {userId} = postAction.payload;
dispatch(fetchUser(userId));
};
execute();
}, []);
Or you can initiate the two requests from two separate useEffect hooks. Your second hook knows when to dispatch based on changes in the Redux state from useSelector.
const Test = () => {
const dispatch = useDispatch();
const post = useSelector((state) => state.slice.post);
const user = useSelector((state) => state.slice.user);
const POST_ID = 1; // just a dummy constant for testing
console.log(post, user);
useEffect(() => {
const loadPost = async () => {
dispatch(fetchPost(POST_ID));
};
loadPost();
}, []); // run once on mount
useEffect(() => {
const loadUser = async (userId) => {
dispatch(fetchUser(userId));
};
// check if post has has been set and has a userId
if ( post && post.userId ) {
loadUser(post.userId);
}
}, [post]); // run when post changes
return <div/>;
};
Here is a complete code example for the two hooks approach:
import {
createAsyncThunk,
configureStore,
createSlice
} from "@reduxjs/toolkit";
import { Provider, useDispatch, useSelector } from "react-redux";
import React, { useEffect } from "react";
export const fetchPost = createAsyncThunk(
"slice/fetchPost",
async (postId) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${postId}`
);
return res.json();
}
);
export const fetchUser = createAsyncThunk(
"slice/fetchUser",
async (userId) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
return res.json();
}
);
const slice = createSlice({
name: "slice",
initialState: {
user: undefined,
post: undefined
},
reducers: {},
extraReducers: {
[fetchPost.fulfilled]: (state, action) => {
state.post = action.payload;
},
[fetchUser.fulfilled]: (state, action) => {
state.user = action.payload;
}
}
});
const store = configureStore({ reducer: { slice: slice.reducer } });
const PostHeader = ({ postId }) => {
const dispatch = useDispatch();
const post = useSelector((state) => state.slice.post);
const user = useSelector((state) => state.slice.user);
console.log(post, user);
useEffect(() => {
const loadPost = async () => {
dispatch(fetchPost(postId));
};
loadPost();
}, [postId, dispatch]); // run once on mount
useEffect(() => {
const loadUser = async (userId) => {
dispatch(fetchUser(userId));
};
// check if post has has been set and has a userId
if (post && post.userId) {
loadUser(post.userId);
}
}, [post, dispatch]); // run when post changes
return (
<div>
{!!post && <h1>{post.title}</h1>}
{!!user && <h2>By {user.name}</h2>}
</div>
);
};
export default function App() {
return (
<Provider store={store}>
<PostHeader postId={1} />
</Provider>
);
}