If I want to dispatch an action whenever a piece of state changes, currently how I do this is by listening to that state inside a component useEffect hook and dispatching an action whenever that state changes.
For example, let's say I have the following slice:
export const updateBar = createAsyncThunk("mySlice/updateBar", async () => {
const { data } = await axios.get("/some/api");
return data;
});
const mySlice = createSlice({
name: "mySlice",
initialState: {
foo: false,
bar: 0,
},
reducers: {
updateFoo: (state, { payload }) => {
state.foo = payload;
},
},
extraReducers: {
[updateBar.fulfilled]: (state, { payload }) => {
state.bar = payload;
},
},
});
Now if I want to update bar whenever foo changes, I have to go to the component side to add the following code:
const Component = () => {
const foo = useSelector((state) => state.mySlice.foo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(updateBar());
}, [dispatch, foo]);
return <div></div>;
};
I am wondering is it possible to call updateBar whenever foo changes within the redux slice and without having to touch the component side, because I think that it would be cleaner if all state side effects are abstracted away from the component.
One solution is to create another thunk encapsulating the two update operations. We do the update foo operation in this thunk and use thunkAPI to dispatch update bar action.
We can continue to keep updateFoo action when you just want to update foo.
Thunk helps us encapsulate the logic of actions. Such as when are actions dispatched, what actions are dispatched, and their relationship, serial dispatch or concurrent dispatch.
E.g.
//@ts-nocheck
import { createAsyncThunk, createSlice, configureStore } from '@reduxjs/toolkit';
const mockApi = async () => {
return { data: 100 };
};
export const updateBar = createAsyncThunk('mySlice/updateBar', async () => {
const { data } = await mockApi();
return data;
});
export const updateFooWithBar = createAsyncThunk('mySlice/updateFooWithBar', (arg, thunkAPI) => {
// If bar depends on foo state or other state,
// you can use thunkAPI.getState() to get the whole state, pick what you need.
thunkAPI.dispatch(updateBar());
return arg;
});
const mySlice = createSlice({
name: 'mySlice',
initialState: {
foo: false,
bar: 0,
},
reducers: {
updateFoo: (state, { payload }) => {
state.foo = payload;
},
},
extraReducers: {
[updateBar.fulfilled]: (state, { payload }) => {
state.bar = payload;
},
[updateFooWithBar.fulfilled]: (state, { payload }) => {
state.foo = payload;
},
},
});
const store = configureStore({ reducer: mySlice.reducer });
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch(updateFooWithBar('next foo')).then(() => {
store.dispatch(mySlice.actions.updateFoo('next next foo'));
});
Execution result:
{ foo: false, bar: 0 }
{ foo: false, bar: 0 }
{ foo: 'next foo', bar: 0 }
{ foo: 'next foo', bar: 100 }
{ foo: 'next next foo', bar: 100 }