Adds automatic loading state to async thunks. Used as a drop-in replacement for @reduxjs/toolkit's createSlice.
If you are setting up a new project, I'd recommend you to rather go for RTK's createAPI. This package was made with the intention for use in existing projects that are otherwise happy to keep using the traditional asyncthunk/slice setup for their API calls.
yarn add @ryfylke-react/create-api-slice
# or
npm i @ryfylke-react/create-api-sliceCreate the slice as you normally would, but make sure state is part of your schema. It should take a type of StateStatus. Use our createAPISlice function instead of createSlice from redux toolkit.
If you want to use another key than
stateto store the StateStatus, pass in{ key: "myCustomKey" }as a second parameter tocreateAPISlice.
slices/postSlice.ts
import {
createAPISlice,
StateStatus,
} from "@ryfylke-react/create-api-slice";
const initialState = {
...baseInitialState,
state: StateStatus.IDLE,
};
const postSlice = createAPISlice({
name: "post",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(getPost.fulfilled, (state, { payload }) => {
state.post = payload;
})
.addCase(getPost.rejected, (state) => {
state.post = null;
});
},
});
export const postReducer = postSlice.reducer;Then, when you create thunks that require loading state, make sure to append :load to the thunk name.
If you want to change the
:loadidentifier, you can pass in{ identifier: ":myCustomIdentifier" }as a second parameter tocreateAPISlice
slices/postThunks.ts
export const getPost = createAsyncThunk(
`post/getPost:load`,
async (slug: string, { rejectWithValue }) => {
return axios
.get<PostData[]>(`${API_URL}/posts?slug=${slug}`)
.then((res) => res.data[0])
.catch((err) => rejectWithValue(err));
}
);Calling dispatch(getPost("...")) will now automatically set the loading state to 1 (PENDING), which will again automatically change to 2 (FULFILLED) or 3 (REJECTED).
Here's how you'd implement this logic on the UI:
// ...
import { StateStatus } from "@ryfylke-react/create-api-slice";
const App = () => {
const { id } = useParams();
const dispatch = useDispatch();
const { state, post } = useSelector((state) => state.post);
useEffect(() => {
dispatch(getPost(id));
}, []);
if (state === StateStatus.REJECTED) {
return "Error from server";
}
if (state === StateStatus.FULFILLED) {
return (...);
}
return "Loading...";
}This unfortunately only supports one concurring loading state per slice. This means that if you call two async thunks that both have :load appended - they will both mutate the same loading state.
If you want, you can add a second parameter to createAPISlice, which is an options object of type APISliceOpts:
type APISliceOpts<T> = {
/** The key that stores the StateStatus in the slice. Default `state`. */
key?: string;
/** The identifier used to add loading state. Default `:load` */
identifier?: string;
/** Replaces the createSlice function used internally */
createSliceOverwrite?: (
options: CreateSliceOptions<T, SliceCaseReducers<T>, string>
) => any;
};