Skip to content

Commit c7c32dd

Browse files
committed
✨ Add initial implementation of task list with SOLID principles and Clean Architecture
1 parent 22a80ea commit c7c32dd

33 files changed

Lines changed: 608 additions & 0 deletions

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
package-lock.json
15+
16+
# Editor directories and files
17+
.vscode/*
18+
!.vscode/extensions.json
19+
.idea
20+
.DS_Store
21+
*.suo
22+
*.ntvs*
23+
*.njsproj
24+
*.sln
25+
*.sw?

README.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# React Clean Architecture Example
2+
3+
This project demonstrates a **Clean Architecture** implementation in React, adhering to the **SOLID** principles. It serves as a reference for structuring a React application with a focus on maintainability, scalability, and separation of concerns.
4+
5+
The application is a simple task list, allowing users to add tasks and view them. This example emphasizes modularity and clear separation of concerns, making it easy to adapt to more complex projects.
6+
7+
## Features
8+
9+
- Add a new task
10+
- Display the list of tasks
11+
12+
### Task Model
13+
14+
The `Task` class is structured as follows:
15+
16+
```ts
17+
export class Task {
18+
constructor(
19+
public id: number,
20+
public name: string,
21+
public completed: boolean = false
22+
) {}
23+
}
24+
```
25+
26+
## Project Structure
27+
28+
The project follows a modular structure based on the Clean Architecture pattern. Below is an overview of the main folders:
29+
30+
```
31+
src/
32+
├── modules/
33+
│ ├── task/
34+
│ │ ├── useCases/
35+
│ │ │ ├── createTaskUseCase.ts
36+
│ │ │ ├── deleteTaskUseCase.ts
37+
│ │ │ ├── getTasksUseCase.ts
38+
│ │ │ ├── refreshTasksAfterCreationUseCase.ts
39+
│ │ ├── di/
40+
│ │ │ └── ioc.ts
41+
│ │ ├── domain/
42+
│ │ │ ├── model/
43+
│ │ │ │ └── Task.ts
44+
│ │ │ └── repository/
45+
│ │ │ └── ITaskRepository.ts
46+
│ │ ├── infrastructure/
47+
│ │ │ ├── dataSources/
48+
│ │ │ │ └── MockDataSource.ts
49+
│ │ │ └── repositories/
50+
│ │ │ └── taskRepository.ts
51+
│ │ ├── presenter/
52+
│ │ │ ├── components/
53+
│ │ │ │ └── TaskForm.tsx
54+
│ │ │ │ └── TaskList.tsx
55+
│ │ │ ├── pages/
56+
│ │ │ │ └── TaskPage.tsx
57+
│ │ │ └── viewModels/
58+
│ │ │ └── taskListViewModel.ts
59+
│ ├── user/ (empty for now, but set up as an example)
60+
├── components/
61+
│ ├── ui/
62+
│ │ ├── Button.tsx (empty for now, but set up as an example)
63+
│ ├── layout/
64+
│ │ ├── Header.tsx (empty for now, but set up as an example)
65+
│ ├── common/
66+
│ │ └── Spinner.tsx (empty for now, but set up as an example)
67+
├── shared/
68+
│ ├── utils/
69+
│ │ └── dateUtils.ts (empty for now, but set up as an example)
70+
│ └── types/
71+
│ └── globalTypes.ts (empty for now, but set up as an example)
72+
└── App.tsx
73+
└── index.css
74+
└── main.tsx
75+
```
76+
77+
## Dependency Injection (IoC)
78+
79+
The `ioc.ts` file is used to register dependencies using **Awilix** for Dependency Injection. Here's the configuration for the container:
80+
81+
```ts
82+
const container = createContainer();
83+
84+
container.register({
85+
// Data source
86+
dataSource: asClass(MockDataSource).singleton(),
87+
88+
// Repository
89+
taskRepository: asFunction(taskRepository),
90+
91+
// Use case
92+
getTasksUseCase: asFunction(getTasksUseCase).singleton(),
93+
createTaskUseCase: asFunction(createTaskUseCase),
94+
refreshTasksAfterCreationUseCase: asFunction(
95+
refreshTasksAfterCreationUseCase
96+
),
97+
98+
// ViewModel
99+
tasksViewModel: asFunction(tasksViewModel),
100+
});
101+
102+
export default container;
103+
```
104+
105+
## Installation
106+
107+
1. Clone the repository:
108+
109+
```bash
110+
git clone https://github.com/Jszigeti/react-clean-architecture-example.git
111+
cd react-clean-architecture-example
112+
```
113+
114+
2. Install dependencies:
115+
116+
```bash
117+
npm install
118+
```
119+
120+
3. Run the development server:
121+
122+
```bash
123+
npm run dev
124+
```
125+
126+
4. Open your browser and go to `http://localhost:3000`.
127+
128+
## Technologies Used
129+
130+
- React 18
131+
- TypeScript
132+
- Awilix (for Dependency Injection)
133+
- Vite (for build and development)
134+
- ESLint (for linting)
135+
136+
## Contributing
137+
138+
Feel free to open an issue or submit a pull request to contribute improvements or features.
139+
140+
## License
141+
142+
This project is licensed under the MIT License.

eslint.config.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import js from '@eslint/js'
2+
import globals from 'globals'
3+
import reactHooks from 'eslint-plugin-react-hooks'
4+
import reactRefresh from 'eslint-plugin-react-refresh'
5+
import tseslint from 'typescript-eslint'
6+
7+
export default tseslint.config(
8+
{ ignores: ['dist'] },
9+
{
10+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
11+
files: ['**/*.{ts,tsx}'],
12+
languageOptions: {
13+
ecmaVersion: 2020,
14+
globals: globals.browser,
15+
},
16+
plugins: {
17+
'react-hooks': reactHooks,
18+
'react-refresh': reactRefresh,
19+
},
20+
rules: {
21+
...reactHooks.configs.recommended.rules,
22+
'react-refresh/only-export-components': [
23+
'warn',
24+
{ allowConstantExport: true },
25+
],
26+
},
27+
},
28+
)

index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + React + TS</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>

package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "react-nvvm",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc -b && vite build",
9+
"lint": "eslint .",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"awilix": "^9.0.0",
14+
"react": "^18.3.1",
15+
"react-dom": "^18.3.1"
16+
},
17+
"devDependencies": {
18+
"@eslint/js": "^9.11.1",
19+
"@types/react": "^18.3.10",
20+
"@types/react-dom": "^18.3.0",
21+
"@vitejs/plugin-react": "^4.3.2",
22+
"eslint": "^9.11.1",
23+
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
24+
"eslint-plugin-react-refresh": "^0.4.12",
25+
"globals": "^15.9.0",
26+
"typescript": "^5.5.3",
27+
"typescript-eslint": "^8.7.0",
28+
"vite": "^5.4.8"
29+
}
30+
}

public/vite.svg

Lines changed: 1 addition & 0 deletions
Loading

src/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import TaskPage from "./modules/task/presenter/pages/TaskPage";
2+
3+
function App() {
4+
return (
5+
<>
6+
<TaskPage />
7+
</>
8+
);
9+
}
10+
11+
export default App;

src/assets/react.svg

Lines changed: 1 addition & 0 deletions
Loading

src/components/common/Spinner.tsx

Whitespace-only changes.

src/components/layout/Header.tsx

Whitespace-only changes.

0 commit comments

Comments
 (0)