The purpose of this repository is to build a front end application with react, using the Clean Architecture, Event Driven Architecture, TDD and vertical slices principles.
This architecture have been showcased in the "CTO InShape" podcast during a pair programming session with Mathieu Kahlaoui! You can watch the podcast here: CTO In Shape - Clean Architecture with Redux.
This project is exploratory and aims to experiment with different ways to structure a front end application and use of TDD. It's my way of doing it and it can surely be better, so feel free to contribute, provide feedback or start PR if you have any ideas, suggestions, or believe something can be improved!
The React components and React Native screens are not the main focus of this project and have been kept simple. So you may still encounter some TODO items or TypeScript warnings / errors (even some "any", oops) in the UI layer. Also there is not test for react component.
| JS | TS | React | Redux | React Native | Jest |
|---|---|---|---|---|---|
npm install
npm run start
npm run test
Clean architecture is often associated with back-end development. Using it on the front end is sometimes seen as overkill, because for a lot of people the frontend is easy. Maybe it was when SSR was the norm. But in a lot of projects using a separate frontend Single Page App (react, View, Angular etc), it's not the case anymore.
And implementing Clean Architecture is not necessary complex, and it can be very, very useful, event in frontend apps.
The main idea here is to separate the React component from the business logic and the data access. This way, we can develop and test the business logic without needing to open the browser or having to wait for a backend.
Imagine this user story: “As a user, I want to create an exercise.”
The business logic is to create an exercise. The data access involves sending a fetch request to create an exercises. The React component is responsible for displaying an form and allowing the user to create a new exercise.
So we can already separate the code into three parts:
We can create a class or function called CreateExerciseUseCase that validates the data sent by the user and creates the exercise by calling the repository. The repository contains the fetch function to actually create the exercise in the backend.
In practice, the UI calls the use case, and the use case calls the repository. Because the use case is coupled to the repository, it cannot be easily tested without sending a request to the backend.
To solve this, we can apply the Dependency Inversion Principle (DIP) and inject the repository into the use case via an interface (here). This way, we can mock the repository: either for developping the frontend without having a backend yet with a in-memory implementation (useful for rapid Proof of Value), or in tests with fakes, allowing us to test the use case without actually making real requests.
Annnnd that’s it. We have a Clean Architecture approach in the frontend as the dependencies go inward.
(image from Milan Jovanović blog)
Please note that the layers here are not defined with traditional folders like application, domain, infrastructure, etc., but Clean Architecture principles are still respected. A vertical slices approach is used instead to gather nearly all the code related to a feature in one place (here) and it's not contradictory to the Clean Architecture. As Robert C. Martin says: “The architecture should scream the use cases of the application.” (Robert C. Martin).
But as the state was managed by React, it's still difficult to test the state changes and the transitions between states. That's when Redux comes in.
To store data in our application (e.g., the logged-in user, a list of exercises, etc.), we need state. To share this state between components, we can use the Context API or a state management library such as Zustand, Recoil, or Redux. Redux has a reputation for having a lot of boilerplate and is sometimes considered overkill.
So why choose Redux? Because Redux is not just a state manager. In this app, we use Redux and RTK for four main purposes:
1) State Management: to store the global application state. 2) (Kind of) Pub/Sub System / Synchronous event bus: In Redux we dispatch actions. This actions are "listened" by reducers in order to update the state accordingly. This part of Redux can be seen as a synchronous event bus. So in this repository, the actions dispatched by the use case are named "event" (here), because they feel more like it (in a event driven architecture style).
image from Yazan Alaboudi Redux talk
3) Dependency Injection: By using the extraArgument option in the Redux store creation (using RTK configure store, here), we can inject the repository (for data fetching, etc.) or any other infrastucture dependency into the use case (here). 4) Middleware for Side Effects: Redux Thunk handles side effects such as API calls. Therefore, our use cases are implemented as Redux thunks, providing more granular control over how Redux actions (events) are dispatched related to the side effects (here).

It's important to notice that Redux lives in our domain / application layers. It's not considered as an infrastructure tool, but as a core part of our application. It's a pragmatic choice for reducing the complexity.
I feel like Redux perfectly fills the missing holes with Clean Architecture with React. The event driven architecture which is enabled by Redux allows us to manage the state in a predictable way. And to think about state and transition without React in mind. That way, we can focus on the business logic and the state transitions, and test them without needing to open the browser. We can also modelize the state transitions with a state machine diagram, which is a great way to visualize the application flow, and using TDD to develop the use cases and state changes. When done, we just need to plug in the React component.
So React is used only for what it was designed for: the UI.
Please note that: - If other events related to another feature need to be dispatched after an event (e.g., creating an exercise triggers a new fetch of exercises), they are dispatched within the same use case. Because a use case does not calls another use case. And React component should not manage the application flow: it's not his responsibility call a use case after one is done. - The state in this project is not normalized (using Normalizer for exemple) and the ui state is not separated from the "entity" state in the store (but it can be if the relational / nested data become is too complex) - The selectors in this project are not created using createSelector (Reselect) because the data retrieved from the store is not derived or transformed
---
title: Create Exercice State
---
flowchart TD
A[
Idle
Status: idle
Error: null
Notifications: n
List Exercices Data: n
]
B[
Loading
Status: loading
Error: null
Notifications: n
List Exercices Data: n
]
C[
Error
Status: error
Error: error message
Notification: n + 1 error
List Exercices Data: n
]
D[
Success
Status: success
Error: null
Notification: n + 1 success
]
E[
List exercices Success
...
Data: n + created exercice
]
subgraph Create Exercice
A -->|Exercice creation Started|B
B -->|Exercice creation failed|C
B -->|Exercice Created|D
end
subgraph List exercices
D -->|...|E
end
The unit tests here are usually socials (and can serve as specifications) in order to avoid fragile tests because of "behavior sensitivity" : test should change if the scenario change, not if the inner code change (check the awesome Ian Cooper's "TDD Revisited" talk).
In this repo, the use case is the starting point and it asserts against the current state with selectors.
sequenceDiagram autonumber
box rgb(211,69,92) UI
participant REACT COMPONENT
end
box rgb(32,120,103) APPLICATION
participant USE CASE (THUNK)
participant EVENT
p
$ claude mcp add react-redux-clean-architecture-tdd-eda-vertical-slices \
-- python -m otcore.mcp_server <graph>