I will just note that I understand the standard data flow of Redux, which you have an action creator which notify the type to the reducer and the reducer will do the magic of updating the state.
yet in React-Redux (redux-toolkit) when I dispatch something it will be a reducer directly, thus conceptually the action creator is missing when I think about it and I'm struggling to understand how the 'reducer' determine the type of action.
in the example below I'm dispatching setRegistrationData which is a reducer not an action creator.
Some code examples to see if my code is incorrect rather then my understanding:
Dispatching the reducer:
dispatch(setRegistrationData())
Store:
const store : Store = configureStore({
reducer: {
modalWindow: modalWindowReducer,
yPosition : windowYPositionSlice,
loginData: loginDataSlice,
formValidation : loginFormValidationSlice,
registrationData : registrationDataSlice,
}
})
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
export default store;
A random Slice
// interface RegistrationData
interface RegistrationData{
userName : string;
email : string;
password : string;
confirmPassword : string;
}
// Need to change it to an object
const initialState : RegistrationData = {
userName : '',
email : '',
password : '',
confirmPassword : ''
};
export const registrationData = createSlice({
name: 'registrationData',
initialState,
reducers: {
setRegistrationData(state){
Object.assign(state, { userName : 'DData', email : '[email protected]', password : 'dddd', confirmPassword : 'ddd'});
}
}
})
export const { setRegistrationData } = registrationData.actions;
export default registrationData.reducer;
Using RTK APIs you abstract some of the old Redux flow (e.g. Action Creators) but what happens under the hood is exactly the same old things.
I think the source of your confusion comes specifically from createSlice
method which is one of the most useful API methods RTK exposes:
const registrationData = createSlice({
name: 'registrationData',
initialState,
reducers: {
setRegistrationData(state){
Object.assign(state, { userName : 'DData', email : '[email protected]', password : 'dddd', confirmPassword : 'ddd'});
}
}
})
What happens here is that you are writing inside the reducers object, a series of micro reducers ( officially named case reducers, since they are like the single case block inside a switch statement of a standard reducer ). The name they are given, will be assigned internally to automatically generated action creators
.
So in the end basically the result of createSlice
is an object that contains a reducer
and an actions
properties and in fact that's what you export from the slice file through destructuring:
export const { setRegistrationData } = registrationData.actions;
The actions object contain the action creators that take the names of the case reducers
written inside the reducers
object, so when you dispatch setRegistrationData
:
dispatch(setRegistrationData(data))
That (data)
will automatically be sent to the setRegistrationData
case reducer since the action.type
is built-in in the action and data will become the payload of your action. In fact if you log the action inside the reducer:
const registrationData = createSlice({
name: 'registrationData',
initialState,
reducers: {
setRegistrationData(state, action){
console.log(action) // {type: "registrationData/setRegistrationData", payload : { userName: ..., email: ..., etc... } }
Object.assign(state, { userName : 'DData', email : '[email protected]', password : 'dddd', confirmPassword : 'ddd'});
}
}
})
You see that the action
has an automatically generated type
with the shape of nameOfReducer/nameOfActionCreator
.
So as you can see the createSlice
method just optimized the code for you and lets you write a lot less code, but under the hood it's the same if you would have written it by hand after all:
const myReducer = (state, action) => {
switch (action.type) {
case "registrationData/setRegistrationData": return {...state, action.payload} // no IMMER
case ... // some other types
}
}
And then build the action creator yourself:
const setRegistrationData = (data) => ({ type: "registrationData/setRegistrationData" , payload: data})
And then dispatch it the same way:
dispatch(setRegistrationData(data))
That's more code, and you can't use IMMER which is built-in in createSlice
method.
So in the end it's just a matter of writing less code, more concise, easier to read and to maintain, but it doesn't change in any way how Redux works and its flow.