├── .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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | --------------------------------------------------------------------------------