├── .eslintrc.json
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
├── App.tsx
├── app
│ ├── hooks.ts
│ └── store.ts
├── favicon.svg
├── features
│ └── counter
│ │ ├── Counter.tsx
│ │ ├── counterAPI.ts
│ │ └── counterSlice.ts
├── index.css
├── main.tsx
└── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:react/recommended",
9 | "plugin:react-hooks/recommended",
10 | "plugin:@typescript-eslint/recommended"
11 | ],
12 | "parser": "@typescript-eslint/parser",
13 | "parserOptions": {
14 | "ecmaFeatures": {
15 | "jsx": true
16 | },
17 | "ecmaVersion": 12,
18 | "sourceType": "module"
19 | },
20 | "plugins": ["react", "@typescript-eslint"],
21 | "rules": {
22 | "@typescript-eslint/explicit-module-boundary-types": "off"
23 | },
24 | "settings": {
25 | "react": {
26 | "version": "detect"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React, Typescript, Tailwindcss, Redux Toolkit, eslint with Vite
2 |
3 | This is a [React](https://reactjs.org) + [TypeScript](https://www.typescriptlang.org/) + [Tailwind](https://tailwindcss.com/) + [RTK](https://redux-toolkit.js.org/) + [eslint](https://eslint.org/) boilerplate built with [Vite](https://vitejs.dev).
4 |
5 | ## What's inside?
6 |
7 | - [ReactJS](https://reactjs.org)
8 | - [Vite](https://vitejs.dev)
9 | - [TypeScript](https://www.typescriptlang.org)
10 | - [Tailwind](https://tailwindcss.com/)
11 | - [RTK](https://redux-toolkit.js.org/)
12 | - [ESLint](https://eslint.org)
13 |
14 | ## Getting started
15 |
16 | 1. Clone the repository.
17 |
18 | ```bash
19 | git clone https://github.com/IsaiaPhiliph/vite-reactts-tailwind-rtk-eslint.git
20 | ```
21 |
22 | 2. Access the project.
23 |
24 | ```bash
25 | cd vite-reactts-tailwind-rtk-eslint
26 | ```
27 |
28 | 3. Make it your own repository
29 |
30 | ```bash
31 | rm -rf .git
32 | git init
33 | ```
34 |
35 | 4. Install dependencies.
36 |
37 | ```bash
38 | npm i
39 | ```
40 |
41 | 5. Start the development server.
42 |
43 | ```bash
44 | npm run dev
45 | ```
46 |
47 | 6. Build for production.
48 |
49 | ```bash
50 | npm run build
51 | ```
52 |
53 | 7. Test your production build.
54 |
55 | ```bash
56 | npm run serve
57 | ```
58 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-reactts-tailwind-rtk-eslint",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "tsc && vite build",
7 | "serve": "vite preview",
8 | "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\""
9 | },
10 | "dependencies": {
11 | "@reduxjs/toolkit": "^1.6.1",
12 | "@vitejs/plugin-react": "^3.0.0",
13 | "react": "^18",
14 | "react-dom": "^18",
15 | "react-redux": "^8.0.2"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.0.17",
19 | "@types/react-dom": "^18.0.9",
20 | "@typescript-eslint/eslint-plugin": "^5.2.0",
21 | "@typescript-eslint/parser": "^5.36.0",
22 | "@vitejs/plugin-react-refresh": "^1.3.1",
23 | "autoprefixer": "^10.4.0",
24 | "eslint": "^8.23.0",
25 | "eslint-plugin-react": "^7.26.0",
26 | "eslint-plugin-react-hooks": "^4.6.0",
27 | "postcss": "^8.4.5",
28 | "tailwindcss": "^3.0.5",
29 | "typescript": "^4.8.2",
30 | "vite": "^4.0.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Counter from "./features/counter/Counter";
3 |
4 | function App(): JSX.Element {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | export default App;
13 |
--------------------------------------------------------------------------------
/src/app/hooks.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
2 | import type { RootState, AppDispatch } from './store';
3 |
4 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
5 | export const useAppDispatch = () => useDispatch();
6 | export const useAppSelector: TypedUseSelectorHook = useSelector;
7 |
--------------------------------------------------------------------------------
/src/app/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
2 | import counterReducer from '../features/counter/counterSlice';
3 |
4 | export const store = configureStore({
5 | reducer: {
6 | counter: counterReducer,
7 | },
8 | });
9 |
10 | export type AppDispatch = typeof store.dispatch;
11 | export type RootState = ReturnType;
12 | export type AppThunk = ThunkAction<
13 | ReturnType,
14 | RootState,
15 | unknown,
16 | Action
17 | >;
18 |
--------------------------------------------------------------------------------
/src/favicon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/features/counter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | import { useAppSelector, useAppDispatch } from "../../app/hooks";
4 | import {
5 | decrement,
6 | increment,
7 | incrementByAmount,
8 | incrementAsync,
9 | incrementIfOdd,
10 | selectCount,
11 | } from "./counterSlice";
12 |
13 | export default function Counter(): JSX.Element {
14 | const count = useAppSelector(selectCount);
15 | const dispatch = useAppDispatch();
16 | const [incrementAmount, setIncrementAmount] = useState("2");
17 |
18 | const incrementValue = Number(incrementAmount) || 0;
19 |
20 | return (
21 |
22 |
23 |
30 |
31 | {count}
32 |
33 |
40 |
41 |
42 | setIncrementAmount(e.target.value)}
46 | className="bg-gray-200 px-4 py-2 w-12 text-gray-600 rounded-sm text-center outline-none focus:outline-none focus:ring-2 ring-purple-600"
47 | />
48 |
54 |
60 |
66 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/features/counter/counterAPI.ts:
--------------------------------------------------------------------------------
1 | // A mock function to mimic making an async request for data
2 | export function fetchCount(amount = 1): Promise<{
3 | data: number;
4 | }> {
5 | return new Promise<{ data: number }>((resolve) =>
6 | setTimeout(() => resolve({ data: amount }), 500)
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/src/features/counter/counterSlice.ts:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
2 | import { RootState, AppThunk } from "../../app/store";
3 | import { fetchCount } from "./counterAPI";
4 |
5 | export interface CounterState {
6 | value: number;
7 | status: "idle" | "loading" | "failed";
8 | }
9 |
10 | const initialState: CounterState = {
11 | value: 0,
12 | status: "idle",
13 | };
14 |
15 | // The function below is called a thunk and allows us to perform async logic. It
16 | // can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
17 | // will call the thunk with the `dispatch` function as the first argument. Async
18 | // code can then be executed and other actions can be dispatched. Thunks are
19 | // typically used to make async requests.
20 | export const incrementAsync = createAsyncThunk(
21 | "counter/fetchCount",
22 | async (amount: number) => {
23 | const response = await fetchCount(amount);
24 | // The value we return becomes the `fulfilled` action payload
25 | return response.data;
26 | }
27 | );
28 |
29 | export const counterSlice = createSlice({
30 | name: "counter",
31 | initialState,
32 | // The `reducers` field lets us define reducers and generate associated actions
33 | reducers: {
34 | increment: (state) => {
35 | // Redux Toolkit allows us to write "mutating" logic in reducers. It
36 | // doesn't actually mutate the state because it uses the Immer library,
37 | // which detects changes to a "draft state" and produces a brand new
38 | // immutable state based off those changes
39 | state.value += 1;
40 | },
41 | decrement: (state) => {
42 | state.value -= 1;
43 | },
44 | // Use the PayloadAction type to declare the contents of `action.payload`
45 | incrementByAmount: (state, action: PayloadAction) => {
46 | state.value += action.payload;
47 | },
48 | },
49 | // The `extraReducers` field lets the slice handle actions defined elsewhere,
50 | // including actions generated by createAsyncThunk or in other slices.
51 | extraReducers: (builder) => {
52 | builder
53 | .addCase(incrementAsync.pending, (state) => {
54 | state.status = "loading";
55 | })
56 | .addCase(incrementAsync.fulfilled, (state, action) => {
57 | state.status = "idle";
58 | state.value += action.payload;
59 | });
60 | },
61 | });
62 |
63 | export const { increment, decrement, incrementByAmount } = counterSlice.actions;
64 |
65 | // The function below is called a selector and allows us to select a value from
66 | // the state. Selectors can also be defined inline where they're used instead of
67 | // in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
68 | export const selectCount = (state: RootState): number => state.counter.value;
69 |
70 | // We can also write thunks by hand, which may contain both sync and async logic.
71 | // Here's an example of conditionally dispatching actions based on current state.
72 | export const incrementIfOdd =
73 | (amount: number): AppThunk =>
74 | (dispatch, getState) => {
75 | const currentValue = selectCount(getState());
76 | if (currentValue % 2 === 1) {
77 | dispatch(incrementByAmount(amount));
78 | }
79 | };
80 |
81 | export default counterSlice.reducer;
82 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./index.css";
3 | import App from "./App";
4 | import { store } from "./app/store";
5 | import { Provider } from "react-redux";
6 |
7 | import { createRoot } from "react-dom/client";
8 |
9 | const container = document.getElementById("root");
10 |
11 | if (!container) throw new Error("Could not find root element with id 'root'");
12 |
13 | const root = createRoot(container);
14 |
15 | root.render(
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.tsx", "./src/**/*.ts"],
3 | theme: {
4 | extend: {},
5 | },
6 | plugins: [],
7 | };
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": false,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react"
18 | },
19 | "include": ["./src"]
20 | }
21 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------