I'm having trouble refactoring with createSlice, I'm a beginner with redux-toolkit and have looked through the documentation but still having problems.if someone could point me in the right direction that would be fantastic. This is the working code
const SET_ALERT = 'setAlert';
const REMOVE_ALERT = 'alertRemoved';
export const setAlert =
(msg, alertType, timeout = 5000) =>
(dispatch) => {
const id = nanoid();
dispatch({
type: SET_ALERT,
payload: { msg, alertType, id },
});
setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
};
const initialState = [];
export default function alertReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_ALERT:
return [...state, payload];
case REMOVE_ALERT:
return state.filter((alert) => alert.id !== payload);
default:
return state;
}
}
Your current setAlert
action creator creates a thunk action (an action which takes dispatch
as an argument) so it cannot be an action creator that is automatically generated by createSlice
.
You can keep the setup very similar to what you have now. You would have two separate actions for setting and removing an alert and a thunk for dispatching both. The underlying basic actions can be created with createSlice
.
import { createSlice, nanoid } from "@reduxjs/toolkit";
const slice = createSlice({
name: "alerts",
initialState: [],
reducers: {
addAlert: (state, action) => {
// modify the draft state and return nothing
state.push(action.payload);
},
removeAlert: (state, action) => {
// replace the entire slice state
return state.filter((alert) => alert.id !== action.payload);
}
}
});
const { addAlert, removeAlert } = slice.actions;
export default slice.reducer;
export const setAlert = (msg, alertType, timeout = 5000) =>
(dispatch) => {
const id = nanoid();
dispatch(addAlert({ msg, alertType, id }));
setTimeout(() => dispatch(removeAlert(id)), timeout);
};
This next section is totally unnecessary and overly "tricky".
We can make use of createAsyncThunk
if we consider opening the alert as the 'pending' action and dismissing the alert as the 'fulfilled' action. It only gets a single argument, so you would need to pass the msg
, alertType
, and timeout
as properties of an object. You can use the unique id of the thunk which is action.meta.requestId
rather than creating your own id. You can also access the arguments of the action via action.meta.arg
.
You can still use createSlice
if you want, though there's no advantage over createReducer
unless you have other actions. You would respond to both of the thunk actions using the extraReducers
property rather than reducers
.
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
export const handleAlert = createAsyncThunk( "alert/set", (arg) => {
const { timeout = 5000 } = arg;
return new Promise((resolve) => {
setTimeout(() => resolve(), timeout);
});
});
export default createReducer(initialState, (builder) =>
builder
.addCase(handleAlert.pending, (state, action) => {
const { alertType, msg } = action.meta.arg;
const id = action.meta.requestId;
// modify the draft state and don't return anything
state.push({ alertType, msg, id });
})
.addCase(handleAlert.fulfilled, (state, action) => {
const id = action.meta.requestId;
// we are replacing the entire state, so we return the new value
return state.filter((alert) => alert.id !== id);
})
);
example component
import { handleAlert } from "../store/slice";
import { useSelector, useDispatch } from "../store";
export const App = () => {
const alerts = useSelector((state) => state.alerts);
const dispatch = useDispatch();
return (
<div>
{alerts.map((alert) => (
<div key={alert.id}>
<strong>{alert.alertType}</strong>
<span>{alert.msg}</span>
</div>
))}
<div>
<button
onClick={() =>
dispatch(
handleAlert({
alertType: "success",
msg: "action was completed successfully",
timeout: 2000
})
)
}
>
Success
</button>
<button
onClick={() =>
dispatch(
handleAlert({
alertType: "warning",
msg: "action not permitted"
})
)
}
>
Warning
</button>
</div>
</div>
);
};
export default App;