Redux with ducks
Context
This blog is about structuring redux state management in your application with ducks pattern This was initially introduced to me by one of my colleague.And since then I have started loving it.You can read more about the ducks pattern proposal here
Pre requisites
This blog is for developers who have already worked with redux.
The code used can be found here, it uses redux with typescript
In the Beginning
State management with Redux requires you to keep adding {actionTypes, actions, reducers}
for state.
This is what my typical redux folder looked like
.
+-- action-types.ts
+-- store.ts
+-- actions
| +-- user-actions.ts
| +-- todo-actions.ts
+-- reducers
| +-- root-reducer.ts
| +-- user-reducer.ts
| +-- todo-reducer.ts
The problems caused by this
- Logic for a model/redux state is divided into multiple places
- Overhead of maintaining multiple files
- Makes it difficult to follow the flow, when calling action from a react component
Implementation with redux ducks
With ducks implementation the same could look like this
.
+-- action-types.ts
+-- store.ts
+-- state.ts
+-- reducer.ts
+-- ducks
| +-- user.ts
| +-- todo.ts
What I Liked
- Merged the logic of a model/redux state of actions and reducer into a single duck file based on the model name.Now the logic of a model is all at on place
- As a rule, no ducks share any imports from each other.These should only contain atomic units, which can then be used to build.This is important for consistency
-
Better flow visibility from the component: Sometimes you may need to update multiple ducks on an action.To solve this we created a new folder called thunks, and this method method is then called called from the component instead of the ducks
A sample thunk
import { doLogin } from "../ducks/user"; import { fetchTodos } from "../ducks/todo"; import { GlobalState } from "../state" export const setUserTodos = ({ userName, password }) => { return async (dispatch: ThunkDispatchType, getState: () => GlobalState) => { await doLogin({ userName, password }); const { user } = getState(); if (user.currentUser) { await fetchTodos(user.currentUser.userId); } } }
For react components updating the state like this highly increases the visibility of what all the action could do.In this case first log in and if logged in fetch todos for this user
- Helped in scaling.This method allowed us to grow the application with ease, without any compromise.
- Better unit testing.In combination with redux-mock-store, we were able to increase confidence by 100% coverage.
Conclusion
I hope this helps in making the decision when you use redux in your application with typescript. Or atleast give you an insight on the ducks proposal.
Also redux toolkit now has a
createSlice
which I think kinda does a similar thing. You can read more about it here