├── .eslintcache ├── .gitignore ├── CONTRIBUTORS.md ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── app │ ├── App.tsx │ ├── useCallback │ │ ├── README.md │ │ └── index.tsx │ ├── useContext │ │ ├── README.md │ │ ├── composition.tsx │ │ ├── index.tsx │ │ ├── provider.tsx │ │ └── providerWCallback.tsx │ ├── useEffectnLayout │ │ ├── README.md │ │ └── index.tsx │ ├── useMemo │ │ ├── README.md │ │ └── index.tsx │ ├── useReducer │ │ ├── README.md │ │ ├── index.tsx │ │ ├── wReducer.tsx │ │ └── wState.tsx │ ├── useRef │ │ ├── README.md │ │ └── index.tsx │ └── useState │ │ ├── README.md │ │ └── index.tsx ├── index.tsx ├── logo.png ├── react-app-env.d.ts └── setupTests.js ├── tsconfig.json └── yarn.lock /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"/develop/learn-react-hooks/hooks/src/index.tsx":"1","/develop/learn-react-hooks/hooks/src/app/App.tsx":"2","/develop/learn-react-hooks/hooks/src/app/useState/index.tsx":"3","/develop/learn-react-hooks/hooks/src/app/useEffectnLayout/index.tsx":"4","/develop/learn-react-hooks/hooks/src/app/useContext/index.tsx":"5","/develop/learn-react-hooks/hooks/src/app/useContext/composition.tsx":"6","/develop/learn-react-hooks/hooks/src/app/useContext/provider.tsx":"7","/develop/learn-react-hooks/hooks/src/app/useContext/providerWCallback.tsx":"8","/develop/learn-react-hooks/hooks/src/app/useReducer/index.tsx":"9","/develop/learn-react-hooks/hooks/src/app/useReducer/wState.tsx":"10","/develop/learn-react-hooks/hooks/src/app/useReducer/wReducer.tsx":"11","/develop/learn-react-hooks/hooks/src/app/useCallback/index.tsx":"12","/develop/learn-react-hooks/hooks/src/app/useMemo/index.tsx":"13","/develop/learn-react-hooks/hooks/src/app/useRef/index.tsx":"14"},{"size":160,"mtime":1608943857509,"results":"15","hashOfConfig":"16"},{"size":1712,"mtime":1609662254145,"results":"17","hashOfConfig":"16"},{"size":397,"mtime":1609685987682,"results":"18","hashOfConfig":"16"},{"size":2531,"mtime":1609685987682,"results":"19","hashOfConfig":"16"},{"size":1475,"mtime":1609685987682,"results":"20","hashOfConfig":"16"},{"size":1043,"mtime":1609685987682,"results":"21","hashOfConfig":"16"},{"size":445,"mtime":1609685987682,"results":"22","hashOfConfig":"16"},{"size":962,"mtime":1609132060460,"results":"23","hashOfConfig":"16"},{"size":462,"mtime":1609207959360,"results":"24","hashOfConfig":"16"},{"size":1372,"mtime":1609207669331,"results":"25","hashOfConfig":"16"},{"size":2180,"mtime":1609220351975,"results":"26","hashOfConfig":"16"},{"size":4103,"mtime":1609685987682,"results":"27","hashOfConfig":"16"},{"size":2500,"mtime":1609685987699,"results":"28","hashOfConfig":"16"},{"size":1682,"mtime":1609685987682,"results":"29","hashOfConfig":"16"},{"filePath":"30","messages":"31","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},"1c1v2ee",{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"47","usedDeprecatedRules":"32"},{"filePath":"48","messages":"49","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},{"filePath":"50","messages":"51","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},{"filePath":"52","messages":"53","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},{"filePath":"54","messages":"55","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"56","usedDeprecatedRules":"32"},{"filePath":"57","messages":"58","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},{"filePath":"59","messages":"60","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"32"},"/develop/learn-react-hooks/hooks/src/index.tsx",[],["61","62"],"/develop/learn-react-hooks/hooks/src/app/App.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useState/index.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useEffectnLayout/index.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useContext/index.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useContext/composition.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useContext/provider.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useContext/providerWCallback.tsx",["63"],"import * as React from 'react';\n\nconst initialStateCallback = {\n callbackState: ''\n}\n\nexport const ContextCallback = React.createContext(undefined);\n\nfunction ContextCallbackProvider(props: React.PropsWithChildren<{}>) {\n const [callbackState, setCallbackState] = React.useState(initialStateCallback);\n\n // With useCallback (prefered)\n const setContextCallback = React.useCallback(\n newState => {\n return setCallbackState({\n callbackState: { ...newState }\n })\n },\n [callbackState, setCallbackState]\n );\n\n const getContextCallback = React.useCallback(\n () => ({ setContextCallback, ...callbackState }),\n [callbackState, setContextCallback]\n );\n\n return (\n \n {props.children}\n \n )\n}\n\nexport default ContextCallbackProvider;","/develop/learn-react-hooks/hooks/src/app/useReducer/index.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useReducer/wState.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useReducer/wReducer.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useCallback/index.tsx",["64"],"import * as React from 'react';\n\nconst functionLogs = new Set();\nconst functionLogsReset = new Set();\n\nconst functionLogsWCallback = new Set();\nconst functionLogsResetWCallback = new Set();\n\nconst UseCallback = () => {\n const staticData = ['Item 1', 'Item 2', 'Item 3'];\n const [items, setItems] = React.useState({\n lists: staticData\n });\n\n const [itemsWCallback, setItemsWCallback] = React.useState({\n lists: staticData\n });\n\n\n const addItems = () => {\n let _placeArray: any = [];\n _placeArray.push(`Item ${items.lists.length + 1}`);\n\n setItems({\n lists: [...items.lists.concat(_placeArray)]\n })\n }\n\n // Recreate addItemsWCallback on every change of itemsWCallback.lists!\n const addItemsWCallback = React.useCallback(() => {\n let _placeArrayWCallback = [];\n _placeArrayWCallback.push(`Item ${itemsWCallback.lists.length + 1}`)\n\n setItemsWCallback({\n lists: [...itemsWCallback.lists.concat(_placeArrayWCallback)]\n })\n }, [itemsWCallback.lists]);\n\n\n const reset = () => {\n setItems({\n lists: staticData\n })\n }\n\n const resetWCallback = React.useCallback(() => {\n setItemsWCallback({\n lists: staticData\n })\n }, [itemsWCallback.lists]);\n\n\n\n // Debug process\n functionLogs.add(addItems);\n functionLogsReset.add(reset);\n functionLogsWCallback.add(addItemsWCallback);\n functionLogsResetWCallback.add(resetWCallback);\n\n // Ketika salah satu function lain dipanggil, maka function yang tanpa menggunakan callback, akan dibuat ulang\n console.log(`Without callback addItems(), created: `, functionLogs.size, ' times');\n console.log(`Without callback reset(), created: `, functionLogsReset.size, ' times');\n\n // Hanya dibuat ulang ketika nilai itemsWCallback.lists berubah (tidak dibuat ulang ketika function lain dipanggil)\n console.log(`With callback addItemsWCallback(), created: `, functionLogsWCallback.size, ' times');\n console.log(`With callback resetWCallback(), created: `, functionLogsResetWCallback.size, ' times');\n\n\n // Divider\n console.log('');\n\n return (\n <>\n

useCallback()

\n
\n
\n

without useCallback()

\n
    \n {items.lists.map((item: any, index: number) => {\n return
  • {item}
  • \n })}\n
\n\n \n \n\n
\n {JSON.stringify(`Without callback addItems(), created: ${functionLogs.size} times`)}
\n {JSON.stringify(`Without callback reset(), created: ${functionLogsReset.size} times`)}\n
\n
\n\n
\n

with useCallback()

\n
    \n {itemsWCallback.lists.map((item: any, index: number) => {\n return
  • {item}
  • \n })}\n
\n\n \n \n\n
\n {JSON.stringify(`With callback addItemsWCallback(), created: ${functionLogsWCallback.size} times`)}
\n {JSON.stringify(`With callback resetWCallback(), created: ${functionLogsResetWCallback.size} times`)}\n
\n
\n
\n \n );\n}\n\nexport default UseCallback;","/develop/learn-react-hooks/hooks/src/app/useMemo/index.tsx",[],"/develop/learn-react-hooks/hooks/src/app/useRef/index.tsx",[],{"ruleId":"65","replacedBy":"66"},{"ruleId":"67","replacedBy":"68"},{"ruleId":"69","severity":1,"message":"70","line":19,"column":9,"nodeType":"71","endLine":19,"endColumn":42,"suggestions":"72"},{"ruleId":"69","severity":1,"message":"73","line":66,"column":8,"nodeType":"71","endLine":66,"endColumn":30,"suggestions":"74"},"no-native-reassign",["75"],"no-negated-in-lhs",["76"],"react-hooks/exhaustive-deps","React Hook React.useCallback has an unnecessary dependency: 'callbackState'. Either exclude it or remove the dependency array.","ArrayExpression",["77"],"React Hook React.useCallback has a missing dependency: 'staticData'. Either include it or remove the dependency array.",["78"],"no-global-assign","no-unsafe-negation",{"desc":"79","fix":"80"},{"desc":"81","fix":"82"},"Update the dependencies array to be: [setCallbackState]",{"range":"83","text":"84"},"Update the dependencies array to be: [staticData]",{"range":"85","text":"86"},[570,603],"[setCallbackState]",[2329,2351],"[staticData]"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | Module React Hooks Deep Dive adalah proyek open source. Siapapun bebas untuk berkontribusi di sini, bisa dalam bentuk perbaikan typo, tambahan penjelasan, maupun submit tulisan baru. Bagi teman-teman yang berminat untuk berkontribusi, silakan fork [https://github.com/natserract/react-hooks-deepdive](https://github.com/natserract/react-hooks-deepdive), kemudian langsung saja buat issue dan relevan pull request untuk issue tersebut 😊. 3 | 4 | Berikut merupakan list nama kontributor yang sudah berbaik hati menyisihkan waktunya untuk membantu pengembangan e-book ini. 5 | - [Alfin Surya](https://github.com/natserract) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Hooks Deep Dive 2 | Hooks merupakan fitur baru di React 16.8. Dengan Hooks, kita dapat menggunakan state dan fitur React yang lain tanpa perlu menulis sebuah kelas baru. Module ini berisi penjelasan bagaimana cara menggunakan hooks disertai dengan contoh penggunaan dalam Bahasa Indonesia. 3 | 4 | Ada total 8 hooks yang dibahas dalam module ini, yaitu: 5 | - [useState](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useState) 6 | - [useEffect](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useEffectnLayout) 7 | - [useLayoutEffect](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useEffectnLayout) 8 | - [useContext](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useContext) 9 | - [useReducer](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useReducer) 10 | - [useCallback](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useCallback) 11 | - [useMemo](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useMemo) 12 | - [useRef](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useRef) 13 | 14 | Hooks memiliki beberapa aturan saat menggunakannya, jika kamu baru mengenal hooks kamu bisa baca terlebih dahulu [rules of hooks](https://reactjs.org/docs/hooks-rules.html). 15 | 16 | ## Kontribusi 17 | Module ini merupakan projek open source, jadi pull request dipersilahkan. Bagi yang ingin berkontribusi bisa langsung cek [https://github.com/natserract/react-hooks-deepdive](https://github.com/natserract/react-hooks-deepdive). Cek juga [laman kontributor](https://github.com/natserract/react-hooks-deepdive/blob/main/CONTRIBUTORS.md) untuk melihat list kontributor. 18 | 19 | ## Referensi 20 | - [https://reactjs.org/docs/hooks-reference.html](https://reactjs.org/docs/hooks-reference.html) 21 | - [https://reactjs.org/docs/hooks-faq.html](https://reactjs.org/docs/hooks-faq.html) 22 | ## Author 23 | Module ini dibuat oleh Alfin Surya. Untuk pertanyaan, kritik, dan saran, silakan pm ke telegram [t.me/natserract](https://t.me/natserract). 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-graphql", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.3.6", 7 | "@apollo/react-common": "^3.1.4", 8 | "@apollo/react-hooks": "^4.0.0", 9 | "@testing-library/jest-dom": "^5.11.4", 10 | "@testing-library/react": "^11.1.0", 11 | "@testing-library/user-event": "^12.1.10", 12 | "@types/jest": "^26.0.19", 13 | "@types/node": "^14.14.13", 14 | "@types/react": "^17.0.0", 15 | "@types/react-dom": "^17.0.0", 16 | "apollo-boost": "^0.4.9", 17 | "graphql": "^15.4.0", 18 | "graphql-tag": "^2.11.0", 19 | "graphql.macro": "^1.4.2", 20 | "node-sass": "^5.0.0", 21 | "react": "^17.0.1", 22 | "react-apollo": "^3.1.5", 23 | "react-dom": "^17.0.1", 24 | "react-hook-form": "^6.13.0", 25 | "react-router-dom": "^5.2.0", 26 | "react-scripts": "4.0.1", 27 | "typescript": "^4.1.3", 28 | "web-vitals": "^0.2.4" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject", 35 | "codegen": "graphql-codegen --config codegen.yml" 36 | }, 37 | "eslintConfig": { 38 | "extends": [ 39 | "react-app", 40 | "react-app/jest" 41 | ] 42 | }, 43 | "browserslist": { 44 | "production": [ 45 | ">0.2%", 46 | "not dead", 47 | "not op_mini all" 48 | ], 49 | "development": [ 50 | "last 1 chrome version", 51 | "last 1 firefox version", 52 | "last 1 safari version" 53 | ] 54 | }, 55 | "devDependencies": { 56 | "@graphql-codegen/typescript": "^1.19.0", 57 | "@types/react-router-dom": "^5.1.6" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natserract/react-hooks-deepdive/10ee0f125f59be3a2d0a207c8a102f557a3309e7/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natserract/react-hooks-deepdive/10ee0f125f59be3a2d0a207c8a102f557a3309e7/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natserract/react-hooks-deepdive/10ee0f125f59be3a2d0a207c8a102f557a3309e7/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/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | BrowserRouter as Router, 4 | Switch, 5 | Route, 6 | Link 7 | } from 'react-router-dom'; 8 | 9 | import UseState from './useState/'; 10 | import UseEffect from './useEffectnLayout'; 11 | import UseContext from './useContext'; 12 | import UseReducer from './useReducer'; 13 | import UseCallback from './useCallback'; 14 | import UseMemo from './useMemo'; 15 | import UseRef from './useRef'; 16 | 17 | const App: React.FunctionComponent = () => { 18 | return ( 19 | 20 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ); 76 | }; 77 | 78 | export default App; 79 | -------------------------------------------------------------------------------- /src/app/useCallback/README.md: -------------------------------------------------------------------------------- 1 | # `useCallback()` 2 | 3 | [useCallback()](https://reactjs.org/docs/hooks-reference.html#usecallback) tujuan dari penggunaan hooks ini adalah untuk **memoisasi**. Sebelum melangkah lebih dalam, alangkah baiknya kita harus pahami dulu apa itu memoisasi. Disini saya akan berikan 2 pengertian, yang sebenernya merujuk pada satu arti: 4 | 5 | 1. Memoisasi adalah istilah yang menggambarkan teknik pengoptimalan tempat Anda menyimpan hasil yang dihitung sebelumnya, dan mengembalikan hasil yang di-cache ketika perhitungan yang sama diperlukan lagi. 6 | 2. Memoisasi adalah tindakan menyimpan hasil masukan dan mengembalikan hasil saat masukan muncul lagi 7 | 8 | Kata kuncinya adalah `menyimpan`, `teknik pengoptimalan`, dan `cache`, mengapa? coba perhatikan kode dibawah ini: 9 | 10 | ```tsx 11 | function List() { 12 | const addItems = () => { 13 | let _placeArray: any = []; 14 | _placeArray.push(`Item ${items.lists.length + 1}`); 15 | 16 | setItems({ 17 | lists: [...items.lists.concat(_placeArray)] 18 | }) 19 | } 20 | 21 | return 22 | } 23 | ``` 24 | 25 | Dan lagi sekali, lihat beberapa himbauan React di [dokumentasinya](https://reactjs.org/docs/faq-functions.html#how-do-i-pass-a-parameter-to-an-event-handler-or-callback) 26 | 27 | > Using in render creates a new function each time the component renders, which may have performance implications. 28 | 29 | > Using an arrow function in render creates a new function each time the component renders, which may break optimizations based on strict identity comparison. 30 | 31 | Jadi maksudnya seperti ini, ketika button di component `List` ini di klik maka akan memanggil fungsi `() => addItems()`. Proses yang terjadi adalah nilai state akan berubah dan terjadi proses perenderan ulang `items.lists.map(() => ...})`. Nah masalahnya adalah ketika komponen `List` ini di render ulang (bukan perpindahan routes, krn setiap pergantian routes komponen selalu dirender ulang), yang terjadi di belakang layar ternyata fungsi `addItems()` ini akan selalu dibuat baru. 32 | 33 | *No bukti, Hoax!* Hmm, okay ini aku kasi buktinya: 34 | 35 | ```tsx 36 | const functionLogs = new Set(); 37 | 38 | functionLogs.add(addItems); 39 | 40 | console.log(`Without callback addItems(), created: `, functionLogs.size, ' times'); 41 | ``` 42 | 43 | Kalau kamu lihat examplenya, coba buka konsol lalu coba klik antara button yang tidak menggunakan callback, dan yang menggunakan `useCallback()`. Coba perhatikan, ketika button tidak pake `useCallback()` di klik maka: 44 | - Nilai pada teks ini `Without callback addItems(), created: 1 times` akan selalu bertambah 45 | - Begitu juga saat button yg pake `useCallback()` di klik, anehnya nilai pada teks `Without callback addItems(), created: 1 times` ini juga ikut bertambah. 46 | 47 | Kalau file `index.tsx` kita kompile, maka hasilnya seperti ini: 48 | 49 | ```js 50 | var addItems = function () { 51 | var _placeArray = []; 52 | _placeArray.push("Item " + (items.lists.length + 1)); 53 | setItems({ 54 | lists: __spreadArrays(items.lists.concat(_placeArray)) 55 | }); 56 | }; 57 | ``` 58 | 59 | Jadi salah satu solusinya adalah membuat [bind(this)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) lalu menyimpannya di constructor `this.addItems = this.addItems.bind(this)`. 60 | 61 | Tapi masalahnya adalah ini `functional` komponen bukan `Class` komponen. So, *why care?, pake arrow function aja gpp* Okay, iya bener gapapa selagi ga ada masalah performance, tpi coba bayangkan saja ketika komponen mempunyai struktur yang kompleks, atau ada proses yang berjalan secara maraton, tentu ini bisa menyebabkan terjadinya memory leaks. Maka harus ada pengoptimalan, krn itu peran hooks `useCallback()` ini tepat untuk digunakan, penggunaannya sendiri mirip seperti `useEffect()`: 62 | 63 | ```tsx 64 | // Recreate addItemsWCallback on every change of itemsWCallback.lists not every component re-rendered! 65 | const addItemsWCallback = React.useCallback(() => { 66 | let _placeArrayWCallback = []; 67 | _placeArrayWCallback.push(`Item ${itemsWCallback.lists.length + 1}`) 68 | 69 | setItemsWCallback({ 70 | lists: [...itemsWCallback.lists.concat(_placeArrayWCallback)] 71 | }) 72 | }, [itemsWCallback.lists]); 73 | 74 | console.log(`With callback addItemsWCallback(), created: `, functionLogsWCallback.size, ' times'); 75 | ``` 76 | 77 | ### Kapan menggunakan `useCallback()` 78 | `useCallback()` mencegah fungsi dibuat ulang lagi saat komponen di render ulang. Hook ini mengembalikan fungsi itu sendiri. Gunakan saat ingin **menyebarkannya ke komponen turunan, dan mencegah dari fungsi yang mahal untuk berjalan kembali**. 79 | 80 | ## Notes 81 | 82 | - Fungsi mahal (expensive function) arti sebenernya adalah fungsi yang banyak menghabiskan banyak waktu dan memori 83 | - Saya kira saat ini kamu sudah cukup paham hooks ini saat kapan digunakan, tetapi jika ingin melihat real case bisa mampir ke artikelnya **Kent C Codds** [disini](https://kentcdodds.com/blog/usememo-and-usecallback/) 84 | - Jangan lupa install extension chrome ini untuk melihat performance dari komponen [react developer tools](https://chrome.google.com/webstore/detail/fmkadmapgofadopljbjfkapdkoienihi) 85 | - Saat event dijalankan / fungsi dipanggil dari masing-masing tipe komponen, lihat bagaimana logsnya untuk tau apa yang terjadi 86 | 87 | ## Next Hooks 88 | [useMemo()](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useMemo) -------------------------------------------------------------------------------- /src/app/useCallback/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const functionLogs = new Set(); 4 | const functionLogsReset = new Set(); 5 | 6 | const functionLogsWCallback = new Set(); 7 | const functionLogsResetWCallback = new Set(); 8 | 9 | const UseCallback = () => { 10 | const staticData = ['Item 1', 'Item 2', 'Item 3']; 11 | const [items, setItems] = React.useState({ 12 | lists: staticData 13 | }); 14 | 15 | const [itemsWCallback, setItemsWCallback] = React.useState({ 16 | lists: staticData 17 | }); 18 | 19 | 20 | const addItems = () => { 21 | let _placeArray: any = []; 22 | _placeArray.push(`Item ${items.lists.length + 1}`); 23 | 24 | setItems({ 25 | lists: [...items.lists.concat(_placeArray)] 26 | }) 27 | } 28 | 29 | // Recreate addItemsWCallback on every change of itemsWCallback.lists! 30 | const addItemsWCallback = React.useCallback(() => { 31 | let _placeArrayWCallback = []; 32 | _placeArrayWCallback.push(`Item ${itemsWCallback.lists.length + 1}`) 33 | 34 | setItemsWCallback({ 35 | lists: [...itemsWCallback.lists.concat(_placeArrayWCallback)] 36 | }) 37 | }, [itemsWCallback.lists]); 38 | 39 | 40 | const reset = () => { 41 | setItems({ 42 | lists: staticData 43 | }) 44 | } 45 | 46 | const resetWCallback = React.useCallback(() => { 47 | setItemsWCallback({ 48 | lists: staticData 49 | }) 50 | }, [itemsWCallback.lists]); 51 | 52 | 53 | 54 | // Debug process 55 | functionLogs.add(addItems); 56 | functionLogsReset.add(reset); 57 | functionLogsWCallback.add(addItemsWCallback); 58 | functionLogsResetWCallback.add(resetWCallback); 59 | 60 | // Ketika salah satu function lain dipanggil, maka function yang tanpa menggunakan callback, akan dibuat ulang 61 | console.log(`Without callback addItems(), created: `, functionLogs.size, ' times'); 62 | console.log(`Without callback reset(), created: `, functionLogsReset.size, ' times'); 63 | 64 | // Hanya dibuat ulang ketika nilai itemsWCallback.lists berubah (tidak dibuat ulang ketika function lain dipanggil) 65 | console.log(`With callback addItemsWCallback(), created: `, functionLogsWCallback.size, ' times'); 66 | console.log(`With callback resetWCallback(), created: `, functionLogsResetWCallback.size, ' times'); 67 | 68 | 69 | // Divider 70 | console.log(''); 71 | 72 | return ( 73 | <> 74 |

useCallback()

75 |
79 |
80 |

without useCallback()

81 |
    82 | {items.lists.map((item: any, index: number) => { 83 | return
  • {item}
  • 84 | })} 85 |
86 | 87 | 88 | 89 | 90 |
91 | {JSON.stringify(`Without callback addItems(), created: ${functionLogs.size} times`)}
92 | {JSON.stringify(`Without callback reset(), created: ${functionLogsReset.size} times`)} 93 |
94 |
95 | 96 |
97 |

with useCallback()

98 |
    99 | {itemsWCallback.lists.map((item: any, index: number) => { 100 | return
  • {item}
  • 101 | })} 102 |
103 | 104 | 105 | 106 | 107 |
108 | {JSON.stringify(`With callback addItemsWCallback(), created: ${functionLogsWCallback.size} times`)}
109 | {JSON.stringify(`With callback resetWCallback(), created: ${functionLogsResetWCallback.size} times`)} 110 |
111 |
112 |
113 | 114 | ); 115 | } 116 | 117 | export default UseCallback; -------------------------------------------------------------------------------- /src/app/useContext/README.md: -------------------------------------------------------------------------------- 1 | # Use Context 2 | Berbicara tentang [`context`](https://reactjs.org/docs/context.html), simpelnya context ini adalah global state manajemen bawaan React. Dengan menggunakan `context`, kita bisa mempasing data/mengirim data dari parent komponen ke child komponen tanpa harus menggunakan `props` untuk melewati level2nya. 3 | 4 | Di dokumentasi React sendiri sempat disinggung mengenai `component tree`, ini menjadi maksud bahwa cara berfikir React, adalah `struktur hierarki`, yang artinya komponen terbagi2 menjadi susunan kecil piece by piece, sub by sub atau level by level. Perumpamaannya seperti ini, ada komponen *orang tua* dan ada komponen *anak-anak*: 5 | 6 | - FilterableProductTable 7 | - SearchBar 8 | - ProductTable 9 | - ProductCategoryRow 10 | - ProductRow 11 | 12 | React menggunakan konsep *props* dan *state* untuk berinteraksi antara komponen dengan proses inputan dan data. Konsep yang cerdas menurut saya, tapi masalahnya adalah ada beberapa pertanyaan *'apakah state ini berlaku di semua komponen? Atau hanya di komponen itu sendiri saja?, Ketika komponen saya nantinya sudah banyak cabang2, banyak melalui level sampai 5 level, apa saya harus menggunakan props untuk sharing input terus menerus?'* 13 | 14 | Jika kita membaca dokumentasi, React memberikan beberapa solusi tepat dari masalah diatas. *'If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context.'* artinya kalau cuman untuk menghindari melewatkan beberapa props melalui banyak level, kita bisa menggunakan [komposisi komponen](https://reactjs.org/docs/composition-vs-inheritance.html) ini cara yang lebih mudah daripada menggunakan context, contohnya ada di file `composition.tsx` 15 | 16 | Namun, bila tujuannya untuk sharing data antar komponen yang sifatnya global seperti: **authentikasi user, tema, cache data, dan language**, maka ini adalah cara yang tepat untuk menggunakan `context`. Inipun kita juga harus hati-hati dan tepat dalam menggunakannya *'apply it sparingly because it makes component reuse more difficult.'*. 17 | 18 | ## Component Composition 19 | Ini sebenernya merupakan sebuah komponen pattern yang digunakan untuk **membangun komponen dari komponen lain menjadi kesatuan yang lebih besar**, istilahnya seperti **gotong royong**. Tetapi, konsep ini punya aturan, casenya seperti ini anggap saja dalam gotong royong terdapat 2 role, role sebagai **ketua kelompok** dan role sebagai **anggota**. 20 | 21 | Aturannya adalah ketika para anggota saling bahu membahu untuk membawa box(misal) maka anggota lain **tidak perlu tahu isi box tersebut apa**. Tugasnya anggota hanya **membawa box tersebut sampai ke tujuan**. Sedangkan ketua kelompok tugasnya mengawasi dan menyiapkan box supaya siap dikirim ke pelanggan, seperti *label, rincian, dll`. 22 | 23 | Jika di realitakan dalam bentuk kode, contohnya seperti dibawah ini: 24 | ```tsx 25 | function Anggota(props){ 26 | return { props.text } 27 | } 28 | 29 | function Anggota_Dua(props){ 30 | return // Cuman sebagai terusan untuk merender 31 | } 32 | 33 | function KetuaKelompok(){ 34 | const btnText = ; 35 | return 36 | } 37 | 38 | 39 | ``` 40 | Pada intinya komposisi komponen ini tujuannya adalah sebagai **terusan** yang dipassing/dikirim melalui `props`. Mirip seperti konsep `{ props.children }`, lebih jelasnya bisa lihat example `composition.tsx`. Cara ini lebih tepat dan mudah dibandingkan dengan `context` 41 | 42 | ## Context 43 | Seperti yang dijelaskan diatas, context adalah global state management. Sebenernya harusnya disini saya langsung saja menjelaskan bagaimana cara dan konsep `useContext()` hooks, cuman karena ada beberapa proses yang berhubungan jadi saya akan jelaskan secara detail. 44 | 45 | Pada beberapa source biasanya menggunakan [useReducer()](https://reactjs.org/docs/hooks-reference.html#usereducer) untuk melakukan manipulasi state, padahal case yang dialami hanya seperti dibawah ini: 46 | 47 | ```tsx 48 | // Reducer 49 | export const Reducer = (state = initialState) => { 50 | ... 51 | case StepActionStatus.ADD_FORM_DATA_PREV: { 52 | return { 53 | ...state, 54 | storedFormData: [...state.storedFormData.concat(action.payload.allFields)].reverse().sort() 55 | } 56 | } 57 | ... 58 | } 59 | ``` 60 | Pointnya tujuan dari kode itu adalah hanya untuk menggabungkan data di store dengan payload (context), tentunya cara ini malah menyulitkan, kalau ada cara yang lebih mudah kenapa cari yang sulit:). Maka dari itu, selagi casenya ga kompleks2 banget, pake `useState()` aja udah cukup (bahkan ini cara yang lebih tepat). 61 | 62 | Ini adalah cara best praktis yang umumnya sering kali digunakan, yaitu dengan menggunakan suatu [`fungsi`](https://reactjs.org/docs/context.html#before-you-use-context) untuk melakukan perubahan nilai pada `context`. Disini saya buat 2 versi, yaitu dengan menggunakan pure `useState`, dan dengan bantuan `useCallback`, Perhatikan kode dibawah ini: 63 | 64 | ```tsx 65 | const initialState = { 66 | state: '' 67 | } 68 | 69 | export const Context = React.createContext(undefined); 70 | 71 | function ContextProvider(props: React.PropsWithChildren<{}>) { 72 | const [state, setState] = React.useState(initialState); 73 | 74 | return ( 75 | 76 | {props.children} 77 | 78 | ) 79 | } 80 | 81 | export default ContextProvider; 82 | 83 | 84 | const contextState = React.useContext(Context); 85 | const { setState } = contextState; 86 | ``` 87 | 88 | Pada kode diatas, value pada `Context.Provider` diambil dari local state, jadi ketika context ini di use maka value yg didapat adalah `state` & `setState`. Seperti yang dijelaskan pada module sebelumnya, `[state, setState]` variabel pertama untuk nilai, variabel kedua untuk merubah nilai. 89 | 90 | Untuk cara yang kedua, hampir mirip dengan cara yang pertama, cuman bedanya ditambah `useCallback`, perhatikan kode dibawah ini: 91 | ```tsx 92 | const initialStateCallback = { 93 | callbackState: '' 94 | } 95 | 96 | export const ContextCallback = React.createContext(undefined); 97 | 98 | function ContextCallbackProvider(props: React.PropsWithChildren<{}>) { 99 | const [callbackState, setCallbackState] = React.useState(initialStateCallback); 100 | 101 | // With useCallback (prefered) 102 | const setContextCallback = React.useCallback( 103 | newState => { 104 | return setCallbackState({ 105 | callbackState: { ...newState } 106 | }) 107 | }, 108 | [callbackState, setCallbackState] 109 | ); 110 | 111 | const getContextCallback = React.useCallback( 112 | () => ({ setContextCallback, ...callbackState }), 113 | [callbackState, setContextCallback] 114 | ); 115 | 116 | return ( 117 | 118 | {props.children} 119 | 120 | ) 121 | } 122 | 123 | export default ContextCallbackProvider; 124 | 125 | 126 | const contextCallbackState = React.useContext(ContextCallback); 127 | const { setContextCallback } = contextCallbackState; 128 | ``` 129 | 130 | Kamu juga bisa menginitialize `useCallback()` nya di komponennya langsung cuman saya lebih prefer di providernya sih. Contoh kodenya seperti ini: 131 | 132 | ```tsx 133 | const handlerCallback = React.useCallback(() => { 134 | setState({ 135 | state: 'Transform to useCallback()', 136 | }) 137 | }, [contextState.state, setState]); 138 | 139 | 140 | ``` 141 | 142 | Alasannya mengapa menggunakan `useCallback()` adalah karena ini *'Using an arrow function in render creates a new function each time the component renders, which may break optimizations based on strict identity comparison.'* artinya arrow function `() => { ... }` ini akan membuat fungsi baru setiap kali komponen dirender ulang, ini bisa menyebabkan [memory leaks](https://www.lambdatest.com/blog/eradicating-memory-leaks-in-javascript/). Lebih jelasnya nanti akan dijelaskan di module selanjutnya mengenai `useCallback()`. 143 | 144 | 145 | ## Next Hooks 146 | [useReducer()](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useReducer) 147 | 148 | -------------------------------------------------------------------------------- /src/app/useContext/composition.tsx: -------------------------------------------------------------------------------- 1 | 2 | /** Example of composition */ 3 | import * as React from 'react'; 4 | 5 | function ContactBrand(props: any) { 6 | return

{props.title}

; 7 | } 8 | 9 | function ContactNavigation(props: any) { 10 | return ( 11 |
12 |
13 | { props.brand } 14 |

{props.desc}

15 |
16 |
17 | ) 18 | } 19 | 20 | 21 | function ContactContainer(props: any) { 22 | return ( 23 | 24 | { props.navigation } 25 | 26 | ) 27 | } 28 | 29 | function Contact(props: any) { 30 | const brand = 31 | const navigation = 32 | return ( 33 |
34 | 35 |
36 | ) 37 | } 38 | 39 | function Page() { 40 | return ( 41 | 45 | ) 46 | } 47 | 48 | 49 | export default Page; -------------------------------------------------------------------------------- /src/app/useContext/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Composition from './composition'; 3 | import ContextProvider, { Context } from './provider'; 4 | import ContextCallbackProvider, { ContextCallback } from './providerWCallback'; 5 | 6 | function UseContext() { 7 | const contextState = React.useContext(Context); 8 | const { setState } = contextState; 9 | 10 | const contextCallbackState = React.useContext(ContextCallback); 11 | const { setContextCallback } = contextCallbackState; 12 | 13 | 14 | const handlerNoCallback = () => setState({ 15 | state: 'Without useCallback()', 16 | }) 17 | 18 | const handlerWCallback = () => setContextCallback({ 19 | callbackState: 'With useCallback()' 20 | }) 21 | 22 | 23 | return ( 24 | 25 |
26 |

Without useCallback

27 | {JSON.stringify(contextState.state)} 28 | 29 |
30 |
31 |

With useCallback

32 | {JSON.stringify(contextCallbackState)} 33 | 34 |
35 | 36 | 37 |
38 | ) 39 | } 40 | 41 | function Page() { 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | } 50 | 51 | 52 | export default Page; -------------------------------------------------------------------------------- /src/app/useContext/provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const initialState = { 4 | state: '' 5 | } 6 | 7 | export const Context = React.createContext(undefined); 8 | 9 | function ContextProvider(props: React.PropsWithChildren<{}>) { 10 | const [state, setState] = React.useState(initialState); 11 | 12 | return ( 13 | 14 | {props.children} 15 | 16 | ) 17 | } 18 | 19 | export default ContextProvider; -------------------------------------------------------------------------------- /src/app/useContext/providerWCallback.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const initialStateCallback = { 4 | callbackState: '' 5 | } 6 | 7 | export const ContextCallback = React.createContext(undefined); 8 | 9 | function ContextCallbackProvider(props: React.PropsWithChildren<{}>) { 10 | const [callbackState, setCallbackState] = React.useState(initialStateCallback); 11 | 12 | // With useCallback (prefered) 13 | const setContextCallback = React.useCallback( 14 | newState => { 15 | return setCallbackState({ 16 | callbackState: { ...newState } 17 | }) 18 | }, 19 | [callbackState, setCallbackState] 20 | ); 21 | 22 | const getContextCallback = React.useCallback( 23 | () => ({ setContextCallback, ...callbackState }), 24 | [callbackState, setContextCallback] 25 | ); 26 | 27 | return ( 28 | 29 | {props.children} 30 | 31 | ) 32 | } 33 | 34 | export default ContextCallbackProvider; -------------------------------------------------------------------------------- /src/app/useEffectnLayout/README.md: -------------------------------------------------------------------------------- 1 | # Use Effect & Use Layout Effect 2 | 3 | Kedua hooks ini sebenernya konsepnya hampir sama, cuman perbedaannya adalah dari bagaimana cara komunikasi dari function ini bekerja, dan timing dalam penggunaannya. So, dicase kali ini dibagian pertama secara konsep kita fokus pada `useEffect` dulu saja karena kedua hooks ini mempunyai konsep yang hampir sama, tapi secara penggunaan nanti akan dijelaskan bagaimana kedua hooks ini digunakan. 4 | 5 | ## `useEffect()` 6 | `useEffect` ini erat kaitannya dengan lifecycle pada react, pada komponen yang berbasis `Class` tahap pada umumnya adalah: 7 | - `componentDidMount()` : Tahap sebelum komponen di render, 8 | - `componentDidUpdate()` : Tahap setelah komponen di render 9 | - `componentWillUnmount()` : Tahap ketika komponen di destroy/unmount 10 | 11 | Sedangkan pada `functional` komponen, React sudah memfasilitasi dengan salah satu hooks andalan yaitu [`useEffect()`](https://reactjs.org/docs/hooks-reference.html#useeffect). Sesuai dengan namanya, fungsinya adalah untuk side effect, contoh: **pemanggilan API, perubahan/manipulasi DOM, timing, read browser storage**, dll. Diluar contoh ini, disarankan tidak menggunakan `useEffect`, karena ada cara yang lebih tepat misal menggunakan `useCallback()`, `useMemo()`, dll. Alasannya terkait dengan optimisasi performance dari React itu sendiri. Lihat kode dibawah ini: 12 | 13 | ```tsx 14 | React.useEffect(() => { 15 | console.log(state); 16 | }, []); 17 | ``` 18 | 19 | Hooks `useEffect` mempunyai 2 parameter, `function (effect: EffectCallback, deps?: DependencyList): void;`. 20 | 21 | ### @effect 22 | Parameter pertama, adalah `effect`, yang sebenernya adalah sebuah imperatif function berupa `callback`, nah didalam callback ini juga ada sebuah function yang tujuannya untuk melakukan pembersihan pada komponen tersebut, artinya *'kalo ga kepake ya buang, atau bersiin'*. Contoh cleanup: 23 | 24 | ```tsx 25 | React.useEffect(() => { 26 | console.log(state); 27 | 28 | return () => { 29 | console.log("Good bye!"); 30 | } 31 | }, []); 32 | ``` 33 | 34 | Cara kerja cleanup ini mirip seperti `componentWillUnmount()`. Fungsi clean up ini tujuannya adalah untuk mencegah memory leak / kebocoran memori. Clean up akan berjalan, jika component di destroy/unmount, coba pindah routes, apa yang terjadi?. Misal tanpa di cleanup, ketika pindah routes, titlenya tidak akan berubah, nilainya masi seperti di komponen yang sebelumnya. 35 | 36 | Maka dari itu, solusinya adalah melalukan pembersihan, ketika component di destroy/unmount maka *do whatever you want*. Beberapa contoh penggunaan cleanup seperti: membatalkan network request saat terjadi error, unsubscribe, menghapus fungsi yang udah gakepake (ex: `removeEventListener()`), dll. 37 | 38 | ### @deps 39 | Parameter kedua, adalah dependensi, artinya `useEffect` akan melakukan tugasnya/merender ulang jika salah satu dependensinya berubah. Misal: 40 | ```tsx 41 | const [state, setState] = React.useState(0); 42 | 43 | React.useEffect(() => { 44 | console.log(state); 45 | }, [state]); 46 | 47 | return 62 | 63 | 64 | 65 | 66 |
67 |

Use Layout Effect

68 |

Bilangan random: {layoutEffectState}

69 |
70 | {/* Nilai 0 disini, cuman formalitas, perubahan value sebenernya ada di bagian useLayoutEffect() */} 71 | 72 |
73 |
74 | 75 | 76 | ) 77 | } 78 | 79 | export default useEffect; -------------------------------------------------------------------------------- /src/app/useMemo/README.md: -------------------------------------------------------------------------------- 1 | # `useMemo()` 2 | 3 | Hook ini memiliki kesamaan dengan hooks `useCallback` yaitu sama-sama untuk optimisasi performance. Perbedaannya [useMemo()](https://reactjs.org/docs/hooks-reference.html#usememo) mengembalikan hasil dari suatu fungsi (ex: `fmt(){ return 'hasil' }`) sedangkan `useCallback` mengembalikan fungsi itu sendiri. 4 | 5 | `useMemo()` memiliki 2 parameter `function useMemo(factory: () => T, deps: DependencyList | undefined): T;` mirip seperti `useEffect`. Hook ini akan berjalan jika salah satu dependensinya berubah. Perhatikan kode dibawah ini: 6 | 7 | ```tsx 8 | function fibonacci(n: any) { 9 | console.log('fibonacci() called'); 10 | 11 | if (n <= 1) { 12 | return 1 13 | } 14 | 15 | return fibonacci(n - 1) + fibonacci(n - 2); 16 | } 17 | 18 | const toggleHandler = () => { 19 | setToggle(!toggle); 20 | } 21 | 22 | const fmt = React.useMemo(() => fibonacci(state), [state]); 23 | ``` 24 | Cara kerja pada kode diatas adalah ketika nilai pada `state` berubah, maka fungsi `fibonacci()` akan dipanggil. Lalu `useMemo` akan mengembalikan hasil dari fungsi `fibonacci()` tersebut `return fibonacci(n - 1) + fibonacci(n - 2);` dan akan menyimpannya dalam memori untuk mencegah fungsi berjalan kembali (ini disebut memoisasi). Dan ketika fungsi `toggleHandler` dijalankan, fungsi `fibonacci()` tidak akan dibuat ulang/dipanggil. 25 | 26 | Yang perlu ditekankan disini, gunakan `useMemo` untuk menjalankan fungsi yang menghabiskan banyak waktu dan memori (expensive function), dan untuk operasi yang berat. Artinya fungsi tersebut memang membutuhkan optimisasi agar tidak merusak kinerja aplikasi. 27 | 28 | ## `React.memo` 29 | [React.memo](https://reactjs.org/docs/react-api.html#reactmemo) merupakan hoc (higher order components) yang tujuannya untuk optimisasi rendering. `React.memo` digunakan dengan tujuan untuk menghindari fungsi render yang berulang-ulang dengan `props` yang sama, jadi kalau `props` atau hasil dari komponen tersebut sama, React akan melewatinya (tidak merender ulang). Namun ketika `props` atau hasilnya berbeda dengan hasil render sebelumnya barulah komponen di render ulang lalu hasilnya di simpan ke dalam memori. 30 | 31 | ```tsx 32 | function Details({ name, title, memo }) { 33 | console.log(memo ? 'DetailsMemoized rendered' : 'Details rendered'); 34 | 35 | return ( 36 |
37 |

Your name is: {name}

38 |

Title: {title}

39 |
40 | ) 41 | } 42 | 43 | const DetailsMemoized = React.memo(Details); 44 | 45 | 46 |
47 | ``` 48 | Untuk lihat hasilnya, kamu bisa coba lihat example bagaimana proses render dari komponen yang di memoisasi dan yang tidak. 49 | 50 | ## Notes 51 | - Semua hooks ini `useMemo`, `useCallback`, `React.memo` tujuannya adalah untuk pengoptimalan kinerja, gunakan dengan tepat jangan terlalu bergantung pada ini. 52 | - Secara default, React.memo hanya akan membandingkan objek dalam props. Jika ingin mengontrol perbandingan, kamu juga dapat menambahkan function perbandingan sebagai argument kedua. [Dokumentasi](https://reactjs.org/docs/react-api.html#reactmemo) 53 | - [When should you not use React.memo](https://stackoverflow.com/questions/53074551/when-should-you-not-use-react-memo) 54 | ## Next Hooks 55 | [useRef()](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useRef) 56 | -------------------------------------------------------------------------------- /src/app/useMemo/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function fibonacci(n: any): any { 4 | console.log('fibonacci() called'); 5 | 6 | if (n <= 1) { 7 | return 1 8 | } 9 | 10 | return fibonacci(n - 1) + fibonacci(n - 2); 11 | } 12 | 13 | function Details({ name, title, memo }: any) { 14 | console.log(memo ? 'DetailsMemoized rendered' : 'Details rendered'); 15 | 16 | return ( 17 |
18 |

Your name is: {name}

19 |

Title: {title}

20 |
21 | ) 22 | } 23 | 24 | const DetailsMemoized = React.memo(Details); 25 | 26 | function UseMemo() { 27 | const [state, setState] = React.useState(0); 28 | const [toggle, setToggle] = React.useState(false); 29 | 30 | const [name, setName] = React.useState('Alfin'); 31 | 32 | const incrementVal = () => { 33 | setState(state - 1) 34 | } 35 | 36 | const decrementVal = () => { 37 | setState(state + 1) 38 | } 39 | 40 | const toggleHandler = () => { 41 | setToggle(!toggle); 42 | } 43 | 44 | // Mengembalikan hasil 45 | const fmt = React.useMemo(() => { 46 | return fibonacci(state) 47 | }, [state]); 48 | 49 | return ( 50 |
51 |
52 |

useMemo()

53 |

{JSON.stringify(fmt)}

54 | 55 | 56 | 57 | 58 |
59 |

(fibonacci() akan dipanggil jika nilai state berubah. Jika tanpa useMemo() ketika button Set Toggle di klik, maka fungsi yang harusnya tidak berkaitan, akan dipanggil juga.)

60 |
61 |
62 |
63 |

React.memo

64 | 65 |
66 | 67 | 68 |
69 |
70 |
71 |

72 | (Ketika button increment / decrement di klik maka komponen details tanpa memoisasi akan dipanggil berulang2) 73 |

74 |

75 | (Namun pada komponen details dengan memoisasi hanya akan dipanggil jika nilai propsnya berubah. Coba klik button change name) 76 |

77 |
78 |
79 |
80 | ) 81 | } 82 | 83 | 84 | 85 | export default UseMemo; -------------------------------------------------------------------------------- /src/app/useReducer/README.md: -------------------------------------------------------------------------------- 1 | # `useReducer()` 2 | 3 | [`useReducer()`](https://reactjs.org/docs/hooks-reference.html#usereducer) merupakan react hooks yang tugasnya mirip seperti `useState()` yaitu sebagai bantuan untuk manajemen state tetapi dengan skala yang lebih besar dan kompleks. Jika kamu pernah menggunakan atau pernah mendengar [redux](https://redux.js.org/) sebelumnya, tentu ini akan terasa familiar. Yang patut diingat, sebenernya reducer adalah hanya sebuah fungsi yang hanya mengembalikan satu nilai saja, bisa berupa bilangan, string, atau object. 4 | 5 | Fungsi useReducer() ini mempunyai 3 parameter, tetapi pada umumnya kebanyakan menggunakan hanya 2 parameter saja 6 | ```tsx 7 | function useReducer>( 8 | reducer: R, 9 | initialState: ReducerState, 10 | initializer?: undefined 11 | ): [ReducerState, Dispatch>]; 12 | ``` 13 | 14 | Dalam implementasinya, `useReducer` di define dgn desctructuring array (seperti `useState()`), 15 | 16 | ```tsx 17 | const [state, dispatch] = React.useReducer(__ReducerName__); 18 | ``` 19 | 20 | Jika useState menggunakan **callback**, useReducer menggunakan **dispatch** untuk melakukan manipulasi statenya. Mungkin ada beberapa pertanyaan, *"mengapa harus memakai useReducer() daripada useState()?, saat kapan menggunakan ini?"*, jawabannya akan menuju pada penjelasan dibawah ini 21 | 22 | Case kali ini adalah melakukan request api, dengan menggunakan beberapa macam state. Disini saya akan buat 2 perbandingan kode antara `useState()` dengan `useReducer()` 23 | 24 | ## Menggunakan `useState()` 25 | Dimulai dengan implementasi penggunaan `useState`, coba lihat kode dibawah ini: 26 | 27 | ```tsx 28 | const [posts, setPosts] = React.useState([]); 29 | const [loading, setLoading] = React.useState(false); 30 | const [errors, setErrors] = React.useState(undefined); 31 | 32 | const fetchRequest = (async () => { 33 | setErrors(''); 34 | setLoading(true); 35 | 36 | try { 37 | let response = await fetch('https://jsonplaceholder.typicode.com/posts'); 38 | if (!response.ok) throw new Error(response.statusText); 39 | 40 | let body = await response.json(); 41 | setLoading(false); 42 | setPosts(body); 43 | 44 | } catch (error) { 45 | setErrors(error.message); 46 | console.error(error); 47 | } 48 | }); 49 | ``` 50 | 51 | Dalam kode diatas terlihat ada 3 macam varian state, yaitu `posts`, `loading`, dan `errors` (ini sebagai contoh, case sebenernya bisa jadi kamu akan menemukan macam varian state yang lebih banyak dan kompleks). Di dalam function `fetchRequest()` proses fetching dilakukan, dari contoh kode diatas saya akan berikan beberapa literasi: 52 | 53 | - Jika kita lihat, untuk melakukan perubahan state kita akan memanggil fungsi set* (setErrors, setLoading, etc), dari sini kita akan berfikir lebih ke **apa yang harus saya lakukan setelah fetch berhasil**, right? 54 | - Dari sisi kode memang terlihat lebih sedikit, tapi ketika state sudah mencapai level kompleks dan banyak, manajemen yang dilakukan **akankah lebih mudah?**, begitu juga dengan proses pengujian / testing. 55 | - Dan coba pahami kata-kata Sunil Pai (React Team) **"Using a reducer helps separate reads, from writes."** 56 | 57 | Setelah cukup puas di bagian `useState()`, coba kita merujuk pada `useReducer()` (sambil mengingat beberapa literasi tadi). 58 | 59 | ## Menggunakan `useReducer()` 60 | Selanjutnya implementasi penggunaan `useReducer`, coba lihat kode dibawah ini: 61 | 62 | ```tsx 63 | const initialState = { 64 | posts: [], 65 | loading: false, 66 | errors: undefined, 67 | }; 68 | 69 | // Create reducer 70 | const Reducer = (state: any = initialState, action: any) => { 71 | switch (action.type) { 72 | case '@@FETCH_REQUEST': { 73 | return { 74 | ...state, 75 | errors: '', 76 | loading: true, 77 | } 78 | } 79 | 80 | case '@@FETCH_SUCCESS': { 81 | return { 82 | ...state, 83 | loading: false, 84 | posts: action.payload.posts 85 | } 86 | } 87 | 88 | case '@@FETCH_ERROR': { 89 | return { 90 | ...state, 91 | loading: false, 92 | errors: action.payload.errors 93 | } 94 | } 95 | 96 | default: return state 97 | } 98 | }; 99 | 100 | ... 101 | // Use reducer & call dispatch 102 | const [state, dispatch] = React.useReducer(Reducer, initialState); 103 | 104 | const fetchRequest = async () => { 105 | dispatch({ type: '@@FETCH_REQUEST' }); 106 | 107 | const response = await fetch('https://jsonplaceholder.typicode.com/posts'); 108 | const body = await response.json(); 109 | 110 | try { 111 | dispatch({ 112 | type: '@@FETCH_SUCCESS', 113 | payload: { 114 | posts: body 115 | } 116 | }); 117 | 118 | } catch (error) { 119 | dispatch({ 120 | type: '@@FETCH_ERROR', 121 | payload: { 122 | errors: error.message 123 | } 124 | }); 125 | } 126 | }; 127 | ... 128 | ``` 129 | 130 | > Penulisan reducer memiliki beberapa gaya penulisan, tapi pada umumnya menggunakan gaya penulisan seperti redux 131 | 132 | Disini saya akan menjelaskan beberapa point penting di reducer: 133 | 134 | - Pada fungsi ini `const Reducer = (state: any = initialState, action: any) => { ... }`, didalamnya ada 2 parameter yaitu parameter `state` untuk nilai / initial state, dan parameter `action` untuk perubahan nilai, mirip seperti (`[state, action] = ...`). 135 | - Mekanisme dari function tersebut adalah ketika `type` nya `@@FETCH_REQUEST` (misal), maka fungsi Reducer() akan mengembalikan nilai sesuai dengan kondisinya. 136 | - Lihat bagian ini `posts: action.payload.posts`, ada kata **payload**. Payload itu ibaratnya seperti titipan, jadi misal pak bos (reducer) menyuruh kita untuk membeli barang, barang yang harus dibeli adalah (payload.posts), jadi nanti toko akan memberikan / mengisi sesuai dengan barang yang diperlukan (posts) 137 | 138 | Ketika reducer udah selesai dibuat, barulah `useReducer` ini akan dipake, contoh: 139 | 140 | ```tsx 141 | const [state, dispatch] = React.useReducer(Reducer, initialState); 142 | 143 | // Actions 144 | dispatch({ 145 | type: '@@FETCH_SUCCESS', 146 | payload: { 147 | posts: body 148 | } 149 | }); 150 | 151 | // View 152 | JSON.stringify(state.posts); 153 | ... 154 | ``` 155 | 156 | Jika useReducer sudah di define, `[state, dispatch]` kedua variabel ini perannya adalah, `state` untuk view, `dispatch` untuk actions / mutasi. Di fungsi `Reducer()` terdapat 3 type: `@@FETCH_REQUEST`, `@@FETCH_SUCCESS`, dan `@@FETCH_ERROR`. Jadi yaudah tinggal panggil dispatchnya, sesuain typenya, isi payloadnya (barangnya), done! 157 | 158 | So, setelah penjelasan yg cukup detail diatas hal yang baru saya sadari adalah bahwa ketika membuat reducer kita akan lebih berfikir **apa yang diperlukan oleh user?** daripada **apa yang harus dilakukan setelahnya** bukan?, ini adalah salah satu jawaban saat kapan reducer itu digunakan, dan kelebihan/kekurangannya. Yang terpenting disesuaikan dengan kebutuhan dan kondisi. Btw kamu juga bisa explore lebih dalam mengenai `useReducer()` ini, misal mengkombinasikannya dengan context, membuat custom hooks, dll. Saya pikir sebelum belajar redux, sebelumnya kamu coba pelajari bagian ini dulu ;). 159 | 160 | ## Next Hooks 161 | [useCallback()](https://github.com/natserract/react-hooks-deepdive/tree/main/src/app/useCallback) 162 | -------------------------------------------------------------------------------- /src/app/useReducer/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import UseState from './wState'; 3 | import UseReducer from './wReducer'; 4 | 5 | function Page() { 6 | return ( 7 | <> 8 |
9 |

Menggunakan useReducer()

10 | 11 |
12 |
13 |

Menggunakan useState()

14 | 15 |
16 | 17 | ) 18 | } 19 | 20 | export default Page; -------------------------------------------------------------------------------- /src/app/useReducer/wReducer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const initialState = { 4 | posts: [], 5 | loading: false, 6 | errors: undefined, 7 | }; 8 | 9 | const Reducer = (state: any = initialState, action: any) => { 10 | switch (action.type) { 11 | case '@@FETCH_REQUEST': { 12 | return { 13 | ...state, 14 | errors: '', 15 | loading: true, 16 | } 17 | } 18 | 19 | case '@@FETCH_SUCCESS': { 20 | return { 21 | ...state, 22 | loading: false, 23 | posts: action.payload.posts 24 | } 25 | } 26 | 27 | case '@@FETCH_ERROR': { 28 | return { 29 | ...state, 30 | loading: false, 31 | errors: action.payload.errors 32 | } 33 | } 34 | 35 | default: return state 36 | } 37 | }; 38 | 39 | export default function UseReducer() { 40 | const [state, dispatch] = React.useReducer>(Reducer, initialState); 41 | 42 | const fetchRequest = async () => { 43 | dispatch({ type: '@@FETCH_REQUEST' }); 44 | 45 | const response = await fetch('https://jsonplaceholder.typicode.com/posts'); 46 | const body = await response.json(); 47 | 48 | try { 49 | dispatch({ 50 | type: '@@FETCH_SUCCESS', 51 | payload: { 52 | posts: body 53 | } 54 | }); 55 | 56 | } catch (error) { 57 | dispatch({ 58 | type: '@@FETCH_ERROR', 59 | payload: { 60 | errors: error.message 61 | } 62 | }); 63 | } 64 | 65 | }; 66 | 67 | React.useEffect(() => { 68 | let isSubsribed = true; 69 | 70 | if (isSubsribed) { 71 | fetchRequest() 72 | } 73 | 74 | return () => { 75 | isSubsribed = false; 76 | } 77 | 78 | }, []); 79 | 80 | if (state.errors) return

Errors: {JSON.stringify(state.errors)}

81 | if (state.loading) return

Loading...

82 | 83 | return ( 84 |
85 |
    86 | {state && state.posts && state.posts.map((item: any, index: number) => ( 87 |
  • 88 | Title: {item.title} 89 |
  • 90 | ))} 91 |
92 |
93 | ) 94 | } -------------------------------------------------------------------------------- /src/app/useReducer/wState.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default function UseState() { 4 | const [posts, setPosts] = React.useState([]); 5 | const [loading, setLoading] = React.useState(false); 6 | const [errors, setErrors] = React.useState(undefined); 7 | 8 | const fetchRequest = (async () => { 9 | setErrors(''); 10 | setLoading(true); 11 | 12 | try { 13 | let response = await fetch('https://jsonplaceholder.typicode.com/posts'); 14 | if (!response.ok) throw new Error(response.statusText); 15 | 16 | let body = await response.json(); 17 | setLoading(false); 18 | setPosts(body); 19 | 20 | } catch (error) { 21 | setErrors(error.message); 22 | console.error(error); 23 | } 24 | }); 25 | 26 | React.useEffect(() => { 27 | let isSubsribed = true; 28 | 29 | if (isSubsribed) { 30 | fetchRequest(); 31 | } 32 | 33 | return () => { 34 | isSubsribed = false; 35 | } 36 | 37 | }, []); 38 | 39 | 40 | if (errors) return

Errors: {JSON.stringify(errors)}

41 | if (loading) return

Loading...

42 | 43 | return ( 44 |
45 |
    46 | {posts && posts.map((item: any, index) => ( 47 |
  • 48 | Title: {item.title} 49 |
  • 50 | ))} 51 |
52 |
53 | ) 54 | } -------------------------------------------------------------------------------- /src/app/useRef/README.md: -------------------------------------------------------------------------------- 1 | # `useRef()` 2 | 3 | Hook yang mengembalikan objek ref berupa properti `.current` yang sifatnya mutable atau bisa diubah. `useRef ()` berguna tidak hanya sekedar attribut `ref`, tapi juga berfungsi untuk mempertahankan nilai yang berubah selama masa hidup komponen. Perhatikan kode dibawah ini: 4 | 5 | ```tsx 6 | const refDOM = React.useRef(null); 7 | const changeTitleDOM = () => { 8 | refDOM.current.innerText = 'Title udah berganti'; 9 | } 10 | 11 |

Title belum berganti

12 | 13 | 14 | ``` 15 | 16 | [useRef](https://reactjs.org/docs/hooks-reference.html#useref) memiliki 1 parameter `function useRef(initialValue: T): MutableRefObject;` berupa initial value `null`. Ketika `refDOM` ini di console, maka akan menampilkan objek properti berupa `.current` dan didalamnya terdapat tag `h3`. 17 | 18 | Pada kode diatas, `ref` digunakan untuk mengakses `DOM` element `h3`, lalu merubah `innerText` nya. Dan ketika button change title di klik maka title akan berganti, kalau kamu lihat secara detail pada bagian `console.log('changeTitleDOM() called?')` ternyata saat pergantian title tidak terjadi proses perenderan ulang. 19 | 20 | Ini adalah salah satu perbedaan antara `useRef` dengan `useState`: 21 | - Jika `useState` menyebabkan perenderan ulang, sedangkan `useRef` tidak menyebabkan perenderan ulang. 22 | - `useRef` berjalan secara synchronous sedangkan `useState` berjalan secara asynchronous 23 | 24 | Perhatikan kode dibawah ini: 25 | ```tsx 26 | const refInitialValue = React.useRef({ 27 | username: 'alfinsurya', 28 | version: 'v17.0.1' 29 | }); 30 | 31 | // Won't be re-rendered 32 | React.useEffect(() => { 33 | refInitialValue.current.username = 'benjamin'; 34 | refInitialValue.current.version = 'v16.8.1'; 35 | }, []); 36 | ``` 37 | Dalam contoh diatas, ref berusaha diperbarui dalam `useEffect` ketika komponen di mount, namun ketika dilihat di DOM perubahan tidak sesuai/tidak muncul. Penyebabnya adalah karena perubahan nilai pada properti `.current` tidak akan menyebabkan perenderan ulang. Ini merupakan cara kerja hooks `useRef`. 38 | 39 | > Keep in mind `useRef` doesn't notify you when its content changes. Mutating `.current` the property doesn't cause a re-render. 40 | 41 | Biasanya yang paling umum `useRef` digunakan untuk: 42 | - Menyimpan data yang tidak membutuhkan render ulang. 43 | - Mengakses DOM seperti mengambil nilai input, mengatur fokus, dll -------------------------------------------------------------------------------- /src/app/useRef/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function UseRef() { 4 | const refDOM = React.useRef(null); 5 | const refInput = React.useRef(null); 6 | const refInitialValue = React.useRef({ 7 | username: 'alfinsurya', 8 | version: 'v17.0.1' 9 | }); 10 | 11 | const changeTitleDOM = () => { 12 | refDOM.current.innerText = 'Title udah berganti'; 13 | } 14 | 15 | const focusHandler = (event: any) => { 16 | event.preventDefault(); 17 | refInput.current.focus(); 18 | 19 | console.log(refInput.current.value); 20 | } 21 | 22 | console.log('changeTitleDOM() called?'); 23 | 24 | // Won't be re-rendered 25 | React.useEffect(() => { 26 | refInitialValue.current.username = 'benjamin'; 27 | refInitialValue.current.version = 'v16.8.1'; 28 | 29 | refInitialValue.current = () => { 30 | console.log('Hello') 31 | refInitialValue.current.version = 'v16.8.1'; 32 | } 33 | }, []); 34 | 35 | return ( 36 |
37 |

useRef()

38 |
    39 |
  • {refInitialValue.current.username}
  • 40 |
  • {refInitialValue.current.version}
  • 41 |
42 |

Title belum berganti

43 | 44 | 45 |

Form

46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 |
54 |
55 | ) 56 | } 57 | 58 | export default UseRef; -------------------------------------------------------------------------------- /src/app/useState/README.md: -------------------------------------------------------------------------------- 1 | # Use State 2 | 3 | State simplenya adalah data, sifatnya mutable / bisa diubah. React basenya adalah komponen by komponen, terdapat 2 jenis komponen, yaitu komponen yg sifatnya stateful, dan ada yg stateless. 4 | - Stateful: Komponen yang sifatnya utk melacak data, mksdnya adalah tempat dimana komponen lain menerima data, istilahnya adalah container. 5 | - Stateless: Bisa juga dibilang komponen statis/dumb, artinya semua keluaran diambil dari props, jadi komponen ini biasanya mempunyai props. 6 | 7 | Fungsi `useState()` merupakan stateful function yang pendefinisannya didefinisikan melalui destructuring array. Contoh pada kode dibawah ini: 8 | ```tsx 9 | const [state, setState] = React.useState(0); 10 | ``` 11 | 12 | Terdapat 2 variabel pada array, yaitu `state`, dan `setState`, pada variabel pertama state adalah *value*, sedangkan setState adalah function untuk merubah *value* state, karena nilainya bersifat mutable/bisa diubah. Dan disana value dari statenya adalah 0 13 | 14 | ## Case 15 | Case kali ini, adalah melakukan pertambahan dan pengurangan bilangan (increment/decrement). Seperti yang dijelaskan diatas, fungsi untuk merubah datanya adalah parameter kedua, yaitu `setState()`. Perhatikan kode dibawah ini: 16 | ```tsx 17 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default useState; -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import ReactDOM from 'react-dom'; 3 | import App from './app/App'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root') 8 | ); -------------------------------------------------------------------------------- /src/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natserract/react-hooks-deepdive/10ee0f125f59be3a2d0a207c8a102f557a3309e7/src/logo.png -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.graphql' { 4 | const content: any; 5 | export = content; 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": false, 4 | "incremental": false, 5 | "target": "es5", 6 | "lib": [ 7 | "dom", 8 | "dom.iterable", 9 | "esnext" 10 | ], 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "declaration": false, 14 | "declarationMap": false, 15 | "isolatedModules": true, 16 | "allowJs": true, 17 | "skipLibCheck": true, 18 | "allowSyntheticDefaultImports": true, 19 | "strict": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "module": "esnext", 23 | "moduleResolution": "node", 24 | "resolveJsonModule": true, 25 | "jsx": "react-jsx" 26 | }, 27 | "exclude": [ 28 | "node_modules" 29 | ], 30 | "include": [ 31 | "next-env.d.ts", 32 | "**/*.ts", 33 | "**/*.tsx" 34 | ], 35 | } --------------------------------------------------------------------------------