├── .editorconfig ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .prettierrc.json ├── README.md ├── artwork.png ├── craco.config.js ├── db.json ├── license ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.final.js ├── App.js ├── Components │ ├── Counter.js │ ├── FabButton.js │ ├── Navbar.js │ ├── RepositortList.js │ └── Repository.js ├── Services │ └── expensiveCalculation.js ├── index.css ├── index.js ├── reportWebVitals.js ├── setupTests.js └── start.js └── tailwind.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | # editorconfig-tools is unable to ignore longs strings or urls 10 | max_line_length = null 11 | 12 | [{*.yml}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | ### Steps to reproduce 16 | 17 | Tell us what steps to reproduce the issue. (If applicable, provide code sample.) 18 | 19 | ### Expected behavior 20 | 21 | Tell us what should happen 22 | 23 | ### Actual behavior 24 | 25 | Tell us what happens instead 26 | 27 | ### System configuration 28 | 29 | **Node version**: 30 | 31 | **npm version**: 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | ### Checklist 16 | 17 | - [ ] `npm start` and/or `npm test` it worked 18 | - [ ] tests and/or benchmarks are included 19 | - [ ] documentation is changed or added 20 | - [ ] commit message follows commit guidelines 21 | 22 | Bug fixes and new features should include tests and possibly benchmarks. 23 | 24 | ### Description of change 25 | 26 | Tell us what changed, created or fixed. (Don't forget to add the link of the issue or a related pull request.) 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | .env*.local 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | .parcel-cache 83 | 84 | # Next.js build output 85 | .next 86 | 87 | # Nuxt.js build / generate output 88 | .nuxt 89 | dist 90 | 91 | # Gatsby files 92 | .cache/ 93 | # Comment in the public line in if your project uses Gatsby and not Next.js 94 | # https://nextjs.org/blog/next-9-1#public-directory-support 95 | # public 96 | 97 | # vuepress build output 98 | .vuepress/dist 99 | 100 | # Serverless directories 101 | .serverless/ 102 | 103 | # FuseBox cache 104 | .fusebox/ 105 | 106 | # DynamoDB Local files 107 | .dynamodb/ 108 | 109 | # TernJS port file 110 | .tern-port 111 | 112 | # Stores VSCode versions used for testing VSCode extensions 113 | .vscode-test 114 | 115 | ### react ### 116 | .DS_* 117 | **/*.backup.* 118 | **/*.back.* 119 | 120 | node_modules 121 | 122 | *.sublime* 123 | 124 | psd 125 | thumb 126 | sketch 127 | 128 | ### vscode ### 129 | .vscode/* 130 | !.vscode/settings.json 131 | !.vscode/tasks.json 132 | !.vscode/launch.json 133 | !.vscode/extensions.json 134 | *.code-workspace -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Controlando Performance com React Memo, useCallback e useMemo 5 | 6 | Nesta aula, criaremos um aplicativo que consumirá dados da API GitHub, 7 | e com isso, seremos capazes de navegar por vários conceitos de **melhoria e análise de desempenho**, 8 | aprenderemos como usar hooks como `useMemo` e `useCallback` a nosso favor para controlar o desempenho das nossas aplicações, 9 | fazendo memoização de cálculos e funções pesadas, também veremos como 10 | analisar nossa renderização com `React Developer Tools` e usar a API `Memo` do React para evitar 11 | renderizações desnecessárias de nossos componentes, no final dessa aula, você será capaz 12 | aplicar esses conceitos para analisar e melhorar o desempenho de seus aplicativos em react. 13 | 14 | 15 | ## Primeiros passos 🏁 16 | 17 | Clone o repositório. 18 | 19 | ```sh 20 | git clone https://github.com/vitormalencar/ 21 | ``` 22 | 23 | `cd` no diretório. 24 | 25 | ```sh 26 | cd react-memoization-hooks 27 | ``` 28 | 29 | Instale as dependências do projeto: 30 | 31 | ```sh 32 | yarn install 33 | 34 | # ou 35 | 36 | npm install 37 | ``` 38 | 39 | Inicie o servidor de desenvolvimento: 40 | 41 | ```sh 42 | yarn start 43 | 44 | # ou 45 | 46 | npm run start 47 | ``` 48 | 49 | Finalmente, vá para [localhost: 3000](http://localhost:3000) no navegador de sua escolha e você está pronto para ir 🚀. 50 | 51 | 💡 **Dica profissional** use o `App.final.js` como guia de referência final, este arquivo contém o projeto final para que você possa acompanhar. 52 | 53 | ## **Opcional** Executando o servidor localmente 📶 54 | 55 | Se você deseja executar o servidor offline: 56 | 57 | ```sh 58 | yarn run start:server 59 | 60 | # ou 61 | 62 | npm run start:server 63 | ``` 64 | 65 | Isso deve abrir um servidor local na porta `3001`, você pode testar acessando 66 | [localhost:3001/repositories](http://localhost:3001/repositories) 67 | se você quiser alterar os dados, pode editar o [`db.json`](./db.json) local. 68 | 69 | Em vez de apontar para a API do github, você deve usar localhost: 70 | 71 | ```diff 72 | # Search 73 | -- const SEARCH = "https://api.github.com/search/repositories"; 74 | ++ const SEARCH = "http://localhost:3001/repositories"; 75 | 76 | # E ao buscar os dados, use 77 | 78 | React.useEffect(() => { 79 | getRepositories(query) 80 | .then((res) => res.json()) 81 | -- .then((data) => setItems((data && data.items) || [])); 82 | ++ .then((data) => setItems((data && data[0].items) || [])); 83 | }, [getRepositories, query]); 84 | 85 | ``` 86 | 87 | ## Ferramentas 🧰 88 | 89 | - [x] React como uma linguagem de IU 90 | - [x] Prettier como formatador de código 91 | - [x] JSON server como servidor local 92 | - [x] TailwindCss UI como nosso kit de ferramentas de design 93 | 94 | ## Estrutura do Projeto 🏗 95 | 96 | O projeto segue um esqueleto regular [create-react-app](https://github.com/facebook/create-react-app) com muito poucas modificações. 97 | 98 | Na pasta src, temos dois diretórios principais: 99 | 100 | - `App.js`: o lugar onde está a lógica principal para este workshop 101 | - `Components /`: componentes reutilizados nas páginas 102 | - `Services /`: que contém, como o nome sugere, funções de serviço de utilidade, 103 | 104 | ## Expert 105 | 106 | | [](https://github.com/vitormalencar) | 107 | | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------: | 108 | | [Vitor Alencar](https://github.com/vitormalencar) | 109 | 110 | 111 | ## Licença 112 | 113 | Projetado com ♥ por [vitormalencar](https://vitormalencar.com). Licenciado sob a [Licença MIT](licença). 114 | -------------------------------------------------------------------------------- /artwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-creators-program/react-memoization-hooks-2021-02-16/c6213372b297def845e65e6e8475da68ddb70cde/artwork.png -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | // craco.config.js 2 | module.exports = { 3 | style: { 4 | postcss: { 5 | plugins: [require("tailwindcss"), require("autoprefixer")], 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2021 vitormalencar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-memoization-hooks", 3 | "description": "react-memoization-hooks", 4 | "version": "0.1.0", 5 | "author": "vitormalencar ", 6 | "browserslist": { 7 | "production": [ 8 | ">0.2%", 9 | "not dead", 10 | "not op_mini all" 11 | ], 12 | "development": [ 13 | "last 1 chrome version", 14 | "last 1 firefox version", 15 | "last 1 safari version" 16 | ] 17 | }, 18 | "dependencies": { 19 | "@craco/craco": "^6.1.1", 20 | "@tailwindcss/postcss7-compat": "^2.0.3", 21 | "@testing-library/jest-dom": "^5.11.4", 22 | "@testing-library/react": "^11.1.0", 23 | "@testing-library/user-event": "^12.1.10", 24 | "autoprefixer": "^9.8.6", 25 | "postcss": "^7.0.35", 26 | "react": "^17.0.1", 27 | "react-dom": "^17.0.1", 28 | "react-scripts": "4.0.2", 29 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3", 30 | "web-vitals": "^1.0.1" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "keywords": [ 39 | "hooks", 40 | "react" 41 | ], 42 | "license": "MIT", 43 | "private": true, 44 | "scripts": { 45 | "eject": "react-scripts eject", 46 | "start": "craco start", 47 | "build": "craco build", 48 | "start:server": "json-server --watch db.json --port 3001" 49 | }, 50 | "devDependencies": { 51 | "husky": ">=4", 52 | "json-server": "^0.16.3", 53 | "lint-staged": ">=10", 54 | "prettier": "2.2.1" 55 | }, 56 | "husky": { 57 | "hooks": { 58 | "pre-commit": "lint-staged" 59 | } 60 | }, 61 | "lint-staged": { 62 | "*.{js,css,md}": "prettier --write" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-creators-program/react-memoization-hooks-2021-02-16/c6213372b297def845e65e6e8475da68ddb70cde/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 19 | 20 | 29 | React App 30 | 31 | 32 | 33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-creators-program/react-memoization-hooks-2021-02-16/c6213372b297def845e65e6e8475da68ddb70cde/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-creators-program/react-memoization-hooks-2021-02-16/c6213372b297def845e65e6e8475da68ddb70cde/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.final.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Navbar from "./Components/Navbar"; 4 | import { HeadCounter } from "./Components/Counter"; 5 | import { FabButton } from "./Components/FabButton"; 6 | 7 | import { likesCounter } from "./Services/expensiveCalculation"; 8 | import { RepositoryList } from "./Components/RepositortList"; 9 | 10 | const SEARCH = "https://api.github.com/search/repositories"; 11 | 12 | function App() { 13 | const [dark, setDark] = React.useState(false); 14 | const [totalLikes, setTotalLikes] = React.useState(0); 15 | 16 | const getRepositories = React.useCallback((query) => { 17 | return fetch(`${SEARCH}?q=${query}`); 18 | }, []); 19 | 20 | const toogleDarkmode = () => setDark(!dark); 21 | 22 | const likes = React.useMemo(() => likesCounter(totalLikes), [totalLikes]); 23 | 24 | const theme = React.useMemo( 25 | () => ({ 26 | color: dark ? "#fff" : "#333", 27 | navbar: dark ? "#1a202c" : "#e5e7eb", 28 | backgroundColor: dark ? "#333" : "#fff", 29 | }), 30 | [dark] 31 | ); 32 | 33 | React.useEffect(() => console.log("Theme updated"), [theme]); 34 | 35 | return ( 36 |
37 | 38 | 39 | 40 | 41 |
42 | ); 43 | } 44 | 45 | export default App; 46 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { HeadCounter } from "./Components/Counter"; 3 | import { FabButton } from "./Components/FabButton"; 4 | import Navbar from "./Components/Navbar"; 5 | import { likesCounter } from "./Services/expensiveCalculation"; 6 | 7 | function App() { 8 | const [totalLikes, setTotalLikes] = useState(0); 9 | const [dark, setDark] = useState(false); 10 | 11 | const likes = likesCounter(totalLikes); 12 | 13 | const theme = { 14 | color: dark ? "#fff" : "#333", 15 | navbar: dark ? "#1a202c" : "#e5e7eb", 16 | backgroundColor: dark ? "#333" : "#fff", 17 | }; 18 | 19 | const toogleDarkmode = () => setDark(!dark); 20 | 21 | return ( 22 |
23 | 24 | 25 | 26 |
27 | ); 28 | } 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /src/Components/Counter.js: -------------------------------------------------------------------------------- 1 | export const HeadCounter = ({ likes }) => ( 2 |
3 |

Total Likes {likes}

4 |
5 | ); 6 | -------------------------------------------------------------------------------- /src/Components/FabButton.js: -------------------------------------------------------------------------------- 1 | export const FabButton = (props) => ( 2 | 8 | ); 9 | -------------------------------------------------------------------------------- /src/Components/Navbar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Navbar({ theme, toogleDarkmode }) { 4 | return ( 5 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/Components/RepositortList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Repository } from "./Repository"; 3 | 4 | export const RepositoryList = React.memo(({ getRepositories }) => { 5 | const [items, setItems] = React.useState([]); 6 | const [query, setquery] = React.useState("facebook"); 7 | 8 | React.useEffect(() => { 9 | getRepositories(query) 10 | .then((res) => res.json()) 11 | .then((data) => setItems((data && data.items) || [])); 12 | }, [getRepositories, query]); 13 | 14 | return ( 15 |
16 | 22 |
23 | {items && 24 | items.map((result) => )} 25 |
26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /src/Components/Repository.js: -------------------------------------------------------------------------------- 1 | const Repository = (result) => ( 2 |
3 |
4 | 10 | {result.full_name} 11 | 12 | {" - "} 13 | {result.stargazers_count} 14 |
15 |

{result.description}

16 |
17 | ); 18 | 19 | export { Repository }; -------------------------------------------------------------------------------- /src/Services/expensiveCalculation.js: -------------------------------------------------------------------------------- 1 | /* 2 | Exemplo de uma operação computacional pesada 3 | esse metodo pode travar sua UI 4 | */ 5 | export const likesCounter = (likesCounter) => { 6 | for (let index = 0; index < 1000000000; index++) {} 7 | return likesCounter + 1; 8 | }; 9 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .App { 6 | overflow: auto; 7 | height: 100vh; 8 | } 9 | .list { 10 | padding-top: 100px; 11 | } 12 | .toggle__dot { 13 | top: -0.25rem; 14 | left: -0.25rem; 15 | transition: all 0.3s ease-in-out; 16 | } 17 | 18 | input:checked ~ .toggle__dot { 19 | transform: translateX(100%); 20 | background-color: #21e372; 21 | } 22 | .repl-list-item { 23 | padding: 1rem; 24 | margin: 1rem; 25 | background: hsl(0, 0%, 81%); 26 | } 27 | 28 | .float-btn { 29 | position: fixed; 30 | width: 60px; 31 | height: 60px; 32 | bottom: 40px; 33 | right: 40px; 34 | border-radius: 50px; 35 | text-align: center; 36 | box-shadow: 2px 2px 3px #999; 37 | } 38 | 39 | .float-btn-rocket { 40 | background-color: #6b7280; 41 | position: fixed; 42 | width: 60px; 43 | height: 60px; 44 | bottom: 120px; 45 | right: 50px; 46 | border-radius: 50px; 47 | text-align: center; 48 | box-shadow: 2px 2px 3px #999; 49 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /src/start.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { HeadCounter } from "./Components/Counter"; 3 | import { FabButton } from "./Components/FabButton"; 4 | import Navbar from "./Components/Navbar"; 5 | import { likesCounter } from "./Services/expensiveCalculation"; 6 | 7 | function App() { 8 | const [totalLikes, setTotalLikes] = useState(0); 9 | const [dark, setDark] = useState(false); 10 | 11 | const likes = likesCounter(totalLikes); 12 | 13 | const theme = { 14 | color: dark ? "#fff" : "#333", 15 | navbar: dark ? "#1a202c" : "#e5e7eb", 16 | backgroundColor: dark ? "#333" : "#fff", 17 | }; 18 | 19 | const toogleDarkmode = () => setDark(!dark); 20 | 21 | return ( 22 |
23 | 24 | 25 | 26 |
27 | ); 28 | } 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | --------------------------------------------------------------------------------