From b33d2fd735e411995feb3c085ddc3524d159319f Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 05:29:30 +0800 Subject: [PATCH 01/14] chore: Display item list in the home page --- client/src/common/layout/head/index.js | 12 ++ client/src/components/home/index.js | 23 ++++ client/src/pages/index.js | 120 +----------------- .../src/pages/usesyncexternalstore/index.js | 15 +++ client/src/styles/Home.module.css | 16 ++- client/src/styles/globals.css | 10 +- 6 files changed, 69 insertions(+), 127 deletions(-) create mode 100644 client/src/common/layout/head/index.js create mode 100644 client/src/components/home/index.js create mode 100644 client/src/pages/usesyncexternalstore/index.js diff --git a/client/src/common/layout/head/index.js b/client/src/common/layout/head/index.js new file mode 100644 index 0000000..13a2683 --- /dev/null +++ b/client/src/common/layout/head/index.js @@ -0,0 +1,12 @@ +import Head from 'next/head' + +export default function HeadComponent () { + return ( + + React Hooks Playground + + + + + ) +} diff --git a/client/src/components/home/index.js b/client/src/components/home/index.js new file mode 100644 index 0000000..aaad4e1 --- /dev/null +++ b/client/src/components/home/index.js @@ -0,0 +1,23 @@ +import Link from 'next/link' +import { Inter } from 'next/font/google' +import styles from '@/styles/Home.module.css' + +const inter = Inter({ subsets: ['latin'] }) + +export default function HomeComponent() { + return ( +
+
+
+

+ React Hooks Playground +

+
+ + + useSyncExternalStore + +
+
+ ) +} diff --git a/client/src/pages/index.js b/client/src/pages/index.js index 36e2dd1..79b8978 100644 --- a/client/src/pages/index.js +++ b/client/src/pages/index.js @@ -1,123 +1,11 @@ -import Head from 'next/head' -import Image from 'next/image' -import { Inter } from 'next/font/google' -import styles from '@/styles/Home.module.css' - -const inter = Inter({ subsets: ['latin'] }) +import HeadComponent from '@/common/layout/head' +import HomeComponent from '@/components/home' export default function Home() { return ( <> - - Create Next App - - - - -
-
-

- Get started by editing  - src/pages/index.js -

-
- - By{' '} - Vercel Logo - -
-
- -
- Next.js Logo -
- 13 -
-
- -
- -

- Docs -> -

-

- Find in-depth information about Next.js features and API. -

-
- - -

- Learn -> -

-

- Learn about Next.js in an interactive course with quizzes! -

-
- - -

- Templates -> -

-

- Discover and deploy boilerplate example Next.js projects. -

-
- - -

- Deploy -> -

-

- Instantly deploy your Next.js site to a shareable URL - with Vercel. -

-
-
-
+ + ) } diff --git a/client/src/pages/usesyncexternalstore/index.js b/client/src/pages/usesyncexternalstore/index.js new file mode 100644 index 0000000..9d94515 --- /dev/null +++ b/client/src/pages/usesyncexternalstore/index.js @@ -0,0 +1,15 @@ +import HeadComponent from '@/common/layout/head' + +function useSyncExternalStoreContainer () { + return ( + <> + + +

+ useSyncExternalStoreContainer +

+ + ) +} + +export default useSyncExternalStoreContainer diff --git a/client/src/styles/Home.module.css b/client/src/styles/Home.module.css index 27dfff5..8073c1c 100644 --- a/client/src/styles/Home.module.css +++ b/client/src/styles/Home.module.css @@ -1,10 +1,10 @@ .main { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; + + height: 100vh; + display: grid; + place-content: center; padding: 6rem; - min-height: 100vh; + } .description { @@ -16,6 +16,7 @@ width: 100%; z-index: 2; font-family: var(--font-mono); + margin: auto; } .description a { @@ -72,6 +73,11 @@ max-width: 30ch; } +.h2 { + text-align: center; + margin-bottom: 24px; +} + .center { display: flex; justify-content: center; diff --git a/client/src/styles/globals.css b/client/src/styles/globals.css index d4f491e..7d2317e 100644 --- a/client/src/styles/globals.css +++ b/client/src/styles/globals.css @@ -1,3 +1,7 @@ +html, body { + font-family: Arial, Helvetica, sans-serif; +} + :root { --max-width: 1100px; --border-radius: 12px; @@ -87,12 +91,6 @@ body { body { color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); } a { From 739f5a39edf3a9c172cf1f9c2aeac983ca1c2a23 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 05:41:58 +0800 Subject: [PATCH 02/14] chore: Create layout components --- client/src/common/layout/page/index.js | 13 +++++++++ client/src/components/home/index.js | 27 ++++++++++--------- .../components/usesyncexternalstore/index.js | 13 +++++++++ client/src/pages/index.js | 8 +----- .../src/pages/usesyncexternalstore/index.js | 16 +++-------- 5 files changed, 46 insertions(+), 31 deletions(-) create mode 100644 client/src/common/layout/page/index.js create mode 100644 client/src/components/usesyncexternalstore/index.js diff --git a/client/src/common/layout/page/index.js b/client/src/common/layout/page/index.js new file mode 100644 index 0000000..5980e0e --- /dev/null +++ b/client/src/common/layout/page/index.js @@ -0,0 +1,13 @@ +import HeadComponent from '../head' + +export default function Page ({ children }) { + return ( +
+ + { children } +
+ ) +} diff --git a/client/src/components/home/index.js b/client/src/components/home/index.js index aaad4e1..46de89c 100644 --- a/client/src/components/home/index.js +++ b/client/src/components/home/index.js @@ -1,4 +1,5 @@ import Link from 'next/link' +import Page from '@/common/layout/page' import { Inter } from 'next/font/google' import styles from '@/styles/Home.module.css' @@ -6,18 +7,20 @@ const inter = Inter({ subsets: ['latin'] }) export default function HomeComponent() { return ( -
-
-
-

- React Hooks Playground -

-
+ +
+
+
+

+ React Hooks Playground +

+
- - useSyncExternalStore - -
-
+ + useSyncExternalStore + +
+
+ ) } diff --git a/client/src/components/usesyncexternalstore/index.js b/client/src/components/usesyncexternalstore/index.js new file mode 100644 index 0000000..4cf93f0 --- /dev/null +++ b/client/src/components/usesyncexternalstore/index.js @@ -0,0 +1,13 @@ +import Page from '@/common/layout/page' + +function UseSyncExternalStoreComponent () { + return ( + +

+ UseSyncExternalStoreComponent +

+
+ ) +} + +export default UseSyncExternalStoreComponent diff --git a/client/src/pages/index.js b/client/src/pages/index.js index 79b8978..5ca4058 100644 --- a/client/src/pages/index.js +++ b/client/src/pages/index.js @@ -1,11 +1,5 @@ -import HeadComponent from '@/common/layout/head' import HomeComponent from '@/components/home' export default function Home() { - return ( - <> - - - - ) + return () } diff --git a/client/src/pages/usesyncexternalstore/index.js b/client/src/pages/usesyncexternalstore/index.js index 9d94515..71ececf 100644 --- a/client/src/pages/usesyncexternalstore/index.js +++ b/client/src/pages/usesyncexternalstore/index.js @@ -1,15 +1,7 @@ -import HeadComponent from '@/common/layout/head' +import UseSyncExternalStoreComponent from '@/components/usesyncexternalstore' -function useSyncExternalStoreContainer () { - return ( - <> - - -

- useSyncExternalStoreContainer -

- - ) +function UseSyncExternalStore () { + return () } -export default useSyncExternalStoreContainer +export default UseSyncExternalStore From 42362b7e9cf827fde85f8381b05653c8e839a1f9 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 10:25:35 +0800 Subject: [PATCH 03/14] chore: Implement usesyncexternalstore from the docs --- .../components/usesyncexternalstore/index.js | 15 ++++++ .../domain/usesyncexternalstore/todostore.js | 49 +++++++++++++++++++ .../src/pages/usesyncexternalstore/index.js | 4 +- 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 client/src/domain/usesyncexternalstore/todostore.js diff --git a/client/src/components/usesyncexternalstore/index.js b/client/src/components/usesyncexternalstore/index.js index 4cf93f0..c140244 100644 --- a/client/src/components/usesyncexternalstore/index.js +++ b/client/src/components/usesyncexternalstore/index.js @@ -1,11 +1,26 @@ import Page from '@/common/layout/page' +import useTodos from '@/domain/usesyncexternalstore/todostore' function UseSyncExternalStoreComponent () { + const { todos, add } = useTodos() + return (

UseSyncExternalStoreComponent

+ + {(todos !== undefined) && +
+
    + {todos.map((item, index) => ( +
  • {item.text}
  • + ))} +
+
+ } + +
) } diff --git a/client/src/domain/usesyncexternalstore/todostore.js b/client/src/domain/usesyncexternalstore/todostore.js new file mode 100644 index 0000000..b0830bd --- /dev/null +++ b/client/src/domain/usesyncexternalstore/todostore.js @@ -0,0 +1,49 @@ +import { useSyncExternalStore } from 'react' + +// https://react.dev/reference/react/useSyncExternalStore + +let nextId = 0 +let todos = [{ id: nextId++, text: 'Todo #1' }] +let listeners = [] + +export default function useTodos () { + const todos = useSyncExternalStore( + todoStore.subscribe, + todoStore.getSnapshot, + todoStore.getServerSnapshot + ) + + return { + todos, + add: todoStore.addTodo + } +} + +export const todoStore = { + addTodo () { + todos = [ ...todos, { id: nextId++, text: 'Todo #' + nextId }] + emitChange() + }, + + subscribe (listener) { + listeners = [...listeners, listener] + + return () => { + listeners = listeners.filter(item => item !== listener) + } + }, + + getSnapshot () { + return todos + }, + + getServerSnapshot () { + return todos + } +} + +function emitChange () { + for (let listener of listeners) { + listener() + } +} diff --git a/client/src/pages/usesyncexternalstore/index.js b/client/src/pages/usesyncexternalstore/index.js index 71ececf..b7ae0fe 100644 --- a/client/src/pages/usesyncexternalstore/index.js +++ b/client/src/pages/usesyncexternalstore/index.js @@ -1,7 +1,9 @@ import UseSyncExternalStoreComponent from '@/components/usesyncexternalstore' function UseSyncExternalStore () { - return () + return ( + + ) } export default UseSyncExternalStore From 35e702c599475182a164c26c93b8876e94b88c49 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 12:20:57 +0800 Subject: [PATCH 04/14] chore: Add a delete method on the external store --- .../components/usesyncexternalstore/index.js | 25 ++++++++++++++----- .../domain/usesyncexternalstore/todostore.js | 8 +++++- .../src/pages/usesyncexternalstore/index.js | 9 ++++++- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/client/src/components/usesyncexternalstore/index.js b/client/src/components/usesyncexternalstore/index.js index c140244..242c640 100644 --- a/client/src/components/usesyncexternalstore/index.js +++ b/client/src/components/usesyncexternalstore/index.js @@ -1,9 +1,11 @@ +import PropTypes from 'prop-types' import Page from '@/common/layout/page' -import useTodos from '@/domain/usesyncexternalstore/todostore' - -function UseSyncExternalStoreComponent () { - const { todos, add } = useTodos() +function UseSyncExternalStoreComponent ({ + todos, + addTodo, + deleteTodo +}) { return (

@@ -14,15 +16,26 @@ function UseSyncExternalStoreComponent () {
    {todos.map((item, index) => ( -
  • {item.text}
  • +
  • + {item.text}   + + + +
  • ))}
} - + ) } +UseSyncExternalStoreComponent.propTypes = { + todos: PropTypes.array, + addTodo: PropTypes.func, + deleteTodo: PropTypes.fun +} + export default UseSyncExternalStoreComponent diff --git a/client/src/domain/usesyncexternalstore/todostore.js b/client/src/domain/usesyncexternalstore/todostore.js index b0830bd..ec64db1 100644 --- a/client/src/domain/usesyncexternalstore/todostore.js +++ b/client/src/domain/usesyncexternalstore/todostore.js @@ -15,7 +15,8 @@ export default function useTodos () { return { todos, - add: todoStore.addTodo + addTodo: todoStore.addTodo, + deleteTodo: todoStore.deleteTodo } } @@ -25,6 +26,11 @@ export const todoStore = { emitChange() }, + deleteTodo (id) { + todos = todos.filter(item => item.id !== id) + emitChange() + }, + subscribe (listener) { listeners = [...listeners, listener] diff --git a/client/src/pages/usesyncexternalstore/index.js b/client/src/pages/usesyncexternalstore/index.js index b7ae0fe..12662d2 100644 --- a/client/src/pages/usesyncexternalstore/index.js +++ b/client/src/pages/usesyncexternalstore/index.js @@ -1,8 +1,15 @@ import UseSyncExternalStoreComponent from '@/components/usesyncexternalstore' +import useTodos from '@/domain/usesyncexternalstore/todostore' function UseSyncExternalStore () { + const { todos, addTodo, deleteTodo } = useTodos() + return ( - + ) } From be5b7d7179e2a9294215c11f00af49603c272a31 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 15:00:21 +0800 Subject: [PATCH 05/14] chore: Create and implement a basic todos redux store --- client/jsconfig.json | 7 +- client/package-lock.json | 155 +++++++++++++++++++++++- client/package.json | 4 +- client/src/components/home/index.js | 9 +- client/src/components/home/items.json | 10 ++ client/src/components/redux/index.js | 36 ++++++ client/src/lib/store/store.js | 18 +++ client/src/lib/store/todos/todoSlice.js | 48 ++++++++ client/src/pages/_app.js | 7 +- client/src/pages/redux/index.js | 29 +++++ 10 files changed, 316 insertions(+), 7 deletions(-) create mode 100644 client/src/components/home/items.json create mode 100644 client/src/components/redux/index.js create mode 100644 client/src/lib/store/store.js create mode 100644 client/src/lib/store/todos/todoSlice.js create mode 100644 client/src/pages/redux/index.js diff --git a/client/jsconfig.json b/client/jsconfig.json index b8d6842..10781e7 100644 --- a/client/jsconfig.json +++ b/client/jsconfig.json @@ -1,7 +1,12 @@ { "compilerOptions": { "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@/hooks/*": ["./src/lib/hooks/*"], + "@/public/*": ["./public/*"], + "@/services/*": ["./src/lib/services/*"], + "@/store/*": ["./src/lib/store/*"], + "@/utils/*": ["./src/lib/utils/*"] } } } diff --git a/client/package-lock.json b/client/package-lock.json index 98675d8..56b640f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,11 +8,13 @@ "name": "client", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^1.9.3", "eslint": "8.36.0", "eslint-config-next": "13.2.4", "next": "13.2.4", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "react-redux": "^8.0.5" }, "engines": { "node": "18.14.2", @@ -371,6 +373,29 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", + "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", + "dependencies": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", @@ -384,11 +409,45 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/parser": { "version": "5.55.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.55.0.tgz", @@ -784,6 +843,11 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -1793,6 +1857,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -1801,6 +1873,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", + "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2735,6 +2816,65 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -2756,6 +2896,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -3206,6 +3351,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/client/package.json b/client/package.json index f3c6a72..81fa310 100644 --- a/client/package.json +++ b/client/package.json @@ -15,10 +15,12 @@ "export": "npm run build && next export" }, "dependencies": { + "@reduxjs/toolkit": "^1.9.3", "eslint": "8.36.0", "eslint-config-next": "13.2.4", "next": "13.2.4", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "react-redux": "^8.0.5" } } diff --git a/client/src/components/home/index.js b/client/src/components/home/index.js index 46de89c..bb4ee5e 100644 --- a/client/src/components/home/index.js +++ b/client/src/components/home/index.js @@ -2,6 +2,7 @@ import Link from 'next/link' import Page from '@/common/layout/page' import { Inter } from 'next/font/google' import styles from '@/styles/Home.module.css' +import navlinks from './items.json' const inter = Inter({ subsets: ['latin'] }) @@ -16,9 +17,11 @@ export default function HomeComponent() {

- - useSyncExternalStore - + {navlinks.map((item, index) => ( + + {item.name} + + ))}
diff --git a/client/src/components/home/items.json b/client/src/components/home/items.json new file mode 100644 index 0000000..6d5b0e2 --- /dev/null +++ b/client/src/components/home/items.json @@ -0,0 +1,10 @@ +[ + { + "name": "useSyncExternalStore", + "link": "/usesyncexternalstore" + }, + { + "name": "redux toolkit", + "link": "/redux" + } +] \ No newline at end of file diff --git a/client/src/components/redux/index.js b/client/src/components/redux/index.js new file mode 100644 index 0000000..860d3ab --- /dev/null +++ b/client/src/components/redux/index.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import Page from '@/common/layout/page' + +function ReduxComponent ({ + todos, + addTodo, + deleteTodo +}) { + return ( + +

+ Redux Toolkit +

+ + + +
    + {(todos).map(((item, id) => ( +
  • + id: {item.id}, {item.text} + + + +
  • + )))} +
+
+ ) +} + +export default ReduxComponent diff --git a/client/src/lib/store/store.js b/client/src/lib/store/store.js new file mode 100644 index 0000000..8e86db4 --- /dev/null +++ b/client/src/lib/store/store.js @@ -0,0 +1,18 @@ +import { combineReducers } from 'redux' +import { configureStore } from '@reduxjs/toolkit' + +import todoSlice from '@/lib/store/todos/todoSlice' + +// Reducers +const combinedReducer = combineReducers({ + todos: todoSlice +}) + +const rootReducer = (state, action) => { + return combinedReducer(state, action) +} + +// Global store +export const store = configureStore({ + reducer: rootReducer +}) diff --git a/client/src/lib/store/todos/todoSlice.js b/client/src/lib/store/todos/todoSlice.js new file mode 100644 index 0000000..8e518ff --- /dev/null +++ b/client/src/lib/store/todos/todoSlice.js @@ -0,0 +1,48 @@ +import { + createSlice, + createEntityAdapter +} from '@reduxjs/toolkit' + +const STATES = { + IDLE: 'idle', + PENDING: 'pending' +} + +const todosAdapter = createEntityAdapter({ + selectId: (todo) => todo.id +}) + +const todoSlice = createSlice({ + name: 'todos', + initialState: todosAdapter.getInitialState({ + loading: STATES.IDLE, + error: '', + success: '', + todo: null + }), + reducers: { + todoReceived (state, action) { + const id = Math.random().toString(36).substring(2, 8) + + state.loading = STATES.IDLE + state.todo = { ...action.payload, id } + todosAdapter.addOne(state, state.todo) + + }, + todoDelete (state, action) { + todosAdapter.removeOne(state, action.payload) + }, + todosReceived (state, action) { + state.loading = STATES.IDLE + todosAdapter.setAll(state, action.payload) + } + } +}) + +export const { + todoReceived, + todosReceived, + todoDelete +} = todoSlice.actions + +export default todoSlice.reducer diff --git a/client/src/pages/_app.js b/client/src/pages/_app.js index 2300201..4b7ffc0 100644 --- a/client/src/pages/_app.js +++ b/client/src/pages/_app.js @@ -1,5 +1,10 @@ +import { Provider } from 'react-redux' +import { store } from '@/store/store' + import '@/styles/globals.css' export default function App({ Component, pageProps }) { - return + return + + } diff --git a/client/src/pages/redux/index.js b/client/src/pages/redux/index.js new file mode 100644 index 0000000..1ed2af7 --- /dev/null +++ b/client/src/pages/redux/index.js @@ -0,0 +1,29 @@ +import { useSelector, useDispatch } from 'react-redux' +import { todoReceived, todoDelete } from '@/lib/store/todos/todoSlice' + +import ReduxComponent from '@/components/redux' + +function ReduxContainer () { + const dispatch = useDispatch() + const todos = useSelector(state => state.todos) + + const addTodo = () => { + dispatch(todoReceived({ + text: 'Hello, world!' + })) + } + + const deleteTodo = (id) => { + dispatch(todoDelete(id)) + } + + return ( + todos.entities[id])} + addTodo={addTodo} + deleteTodo={deleteTodo} + /> + ) +} + +export default ReduxContainer From 679fe7e3df9d97e7bbd1c88ebd1d65ad8141bcd3 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 15:45:12 +0800 Subject: [PATCH 06/14] chore: Minimize full page rerendering --- client/src/common/ui/card/Card.module.css | 7 +++++ client/src/common/ui/card/index.js | 11 ++++++++ .../home}/Home.module.css | 0 client/src/components/home/index.js | 3 ++- client/src/components/redux/index.js | 26 +++++++++--------- client/src/domain/redux/todolist/index.js | 27 +++++++++++++++++++ client/src/lib/store/todos/todoSlice.js | 6 +++++ client/src/pages/redux/index.js | 4 +-- 8 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 client/src/common/ui/card/Card.module.css create mode 100644 client/src/common/ui/card/index.js rename client/src/{styles => components/home}/Home.module.css (100%) create mode 100644 client/src/domain/redux/todolist/index.js diff --git a/client/src/common/ui/card/Card.module.css b/client/src/common/ui/card/Card.module.css new file mode 100644 index 0000000..f4d4f10 --- /dev/null +++ b/client/src/common/ui/card/Card.module.css @@ -0,0 +1,7 @@ +.card { + border: 1px solid; + max-width: 400px; + min-height: 100px; + border-radius: 16px; + padding: 16px; +} \ No newline at end of file diff --git a/client/src/common/ui/card/index.js b/client/src/common/ui/card/index.js new file mode 100644 index 0000000..d1d9b1e --- /dev/null +++ b/client/src/common/ui/card/index.js @@ -0,0 +1,11 @@ +import styles from './Card.module.css' + +function Card ({ children }) { + return ( +
+ {children} +
+ ) +} + +export default Card diff --git a/client/src/styles/Home.module.css b/client/src/components/home/Home.module.css similarity index 100% rename from client/src/styles/Home.module.css rename to client/src/components/home/Home.module.css diff --git a/client/src/components/home/index.js b/client/src/components/home/index.js index bb4ee5e..b6c20cc 100644 --- a/client/src/components/home/index.js +++ b/client/src/components/home/index.js @@ -1,7 +1,8 @@ import Link from 'next/link' import Page from '@/common/layout/page' + import { Inter } from 'next/font/google' -import styles from '@/styles/Home.module.css' +import styles from './Home.module.css' import navlinks from './items.json' const inter = Inter({ subsets: ['latin'] }) diff --git a/client/src/components/redux/index.js b/client/src/components/redux/index.js index 860d3ab..0d73808 100644 --- a/client/src/components/redux/index.js +++ b/client/src/components/redux/index.js @@ -1,8 +1,9 @@ import PropTypes from 'prop-types' import Page from '@/common/layout/page' +import Card from '@/common/ui/card' +import TodoListComponent from '@/domain/redux/todolist' function ReduxComponent ({ - todos, addTodo, deleteTodo }) { @@ -17,20 +18,19 @@ function ReduxComponent ({ Add Todo -
    - {(todos).map(((item, id) => ( -
  • - id: {item.id}, {item.text} - - - -
  • - )))} -
+ +
+ + + hello + ) } +ReduxComponent.propTypes = { + addTodo: PropTypes.func, + deleteTodo: PropTypes.func +} + export default ReduxComponent diff --git a/client/src/domain/redux/todolist/index.js b/client/src/domain/redux/todolist/index.js new file mode 100644 index 0000000..c382d22 --- /dev/null +++ b/client/src/domain/redux/todolist/index.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' + +function TodoListComponent ({ deleteTodo }) { + const {ids, entities: todos } = useSelector(state => state.todos) + + return ( +
    + {(ids).map(((id, index) => ( +
  • + id: {todos[id].id}, {todos[id].text} + + + +
  • + )))} +
+ ) +} + +TodoListComponent.propTypes = { + deleteTodo: PropTypes.func +} + +export default TodoListComponent diff --git a/client/src/lib/store/todos/todoSlice.js b/client/src/lib/store/todos/todoSlice.js index 8e518ff..dc1f51a 100644 --- a/client/src/lib/store/todos/todoSlice.js +++ b/client/src/lib/store/todos/todoSlice.js @@ -1,3 +1,7 @@ +// Notes: +// https://redux.js.org/tutorials/essentials/part-6-performance-normalization#normalized-state-structure +// https://redux.js.org/tutorials/essentials/part-6-performance-normalization#optimizing-the-posts-list + import { createSlice, createEntityAdapter @@ -8,10 +12,12 @@ const STATES = { PENDING: 'pending' } +// Entiti adapter const todosAdapter = createEntityAdapter({ selectId: (todo) => todo.id }) +// Slice const todoSlice = createSlice({ name: 'todos', initialState: todosAdapter.getInitialState({ diff --git a/client/src/pages/redux/index.js b/client/src/pages/redux/index.js index 1ed2af7..d25077b 100644 --- a/client/src/pages/redux/index.js +++ b/client/src/pages/redux/index.js @@ -1,11 +1,10 @@ -import { useSelector, useDispatch } from 'react-redux' +import { useDispatch } from 'react-redux' import { todoReceived, todoDelete } from '@/lib/store/todos/todoSlice' import ReduxComponent from '@/components/redux' function ReduxContainer () { const dispatch = useDispatch() - const todos = useSelector(state => state.todos) const addTodo = () => { dispatch(todoReceived({ @@ -19,7 +18,6 @@ function ReduxContainer () { return ( todos.entities[id])} addTodo={addTodo} deleteTodo={deleteTodo} /> From 7a4710faef844fd7a2df8bf9c1d2b47b30513f5f Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 15:59:36 +0800 Subject: [PATCH 07/14] chore: Minimize full page rerendering from usesyncexternalstore --- .../components/usesyncexternalstore/index.js | 33 +++++++++---------- .../usesyncexternalstore/todolist/index.js | 29 ++++++++++++++++ .../todostore.js => lib/hooks/usetodo.js} | 0 .../src/pages/usesyncexternalstore/index.js | 5 ++- 4 files changed, 46 insertions(+), 21 deletions(-) create mode 100644 client/src/domain/usesyncexternalstore/todolist/index.js rename client/src/{domain/usesyncexternalstore/todostore.js => lib/hooks/usetodo.js} (100%) diff --git a/client/src/components/usesyncexternalstore/index.js b/client/src/components/usesyncexternalstore/index.js index 242c640..f0be97d 100644 --- a/client/src/components/usesyncexternalstore/index.js +++ b/client/src/components/usesyncexternalstore/index.js @@ -1,8 +1,9 @@ import PropTypes from 'prop-types' import Page from '@/common/layout/page' +import Card from '@/common/ui/card' +import TodoListComponentV2 from '@/domain/usesyncexternalstore/todolist' function UseSyncExternalStoreComponent ({ - todos, addTodo, deleteTodo }) { @@ -12,30 +13,26 @@ function UseSyncExternalStoreComponent ({ UseSyncExternalStoreComponent - {(todos !== undefined) && -
-
    - {todos.map((item, index) => ( -
  • - {item.text}   - - - -
  • - ))} -
-
- } + - + + +

+ + + Hello + ) } UseSyncExternalStoreComponent.propTypes = { - todos: PropTypes.array, addTodo: PropTypes.func, - deleteTodo: PropTypes.fun + deleteTodo: PropTypes.func } export default UseSyncExternalStoreComponent diff --git a/client/src/domain/usesyncexternalstore/todolist/index.js b/client/src/domain/usesyncexternalstore/todolist/index.js new file mode 100644 index 0000000..6b85bfd --- /dev/null +++ b/client/src/domain/usesyncexternalstore/todolist/index.js @@ -0,0 +1,29 @@ +import PropTypes from 'prop-types' +import useTodos from '@/lib/hooks/usetodo' + +function TodoListComponentV2 ({ + deleteTodo +}) { + const { todos } = useTodos() + + return ( +
    + {(todos).map(((item, index) => ( +
  • + id: {item.id}, {item.text} + + + +
  • + )))} +
+ ) +} + +TodoListComponentV2.propTypes = { + deleteTodo: PropTypes.func +} + +export default TodoListComponentV2 diff --git a/client/src/domain/usesyncexternalstore/todostore.js b/client/src/lib/hooks/usetodo.js similarity index 100% rename from client/src/domain/usesyncexternalstore/todostore.js rename to client/src/lib/hooks/usetodo.js diff --git a/client/src/pages/usesyncexternalstore/index.js b/client/src/pages/usesyncexternalstore/index.js index 12662d2..078356c 100644 --- a/client/src/pages/usesyncexternalstore/index.js +++ b/client/src/pages/usesyncexternalstore/index.js @@ -1,12 +1,11 @@ import UseSyncExternalStoreComponent from '@/components/usesyncexternalstore' -import useTodos from '@/domain/usesyncexternalstore/todostore' +import useTodos from '@/lib/hooks/usetodo' function UseSyncExternalStore () { - const { todos, addTodo, deleteTodo } = useTodos() + const { addTodo, deleteTodo } = useTodos() return ( From f347657c7bc8229457dc251f87b24308899deb0f Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 16:42:18 +0800 Subject: [PATCH 08/14] chore: Update docs --- client/src/lib/hooks/usetodo.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/client/src/lib/hooks/usetodo.js b/client/src/lib/hooks/usetodo.js index ec64db1..146a1a9 100644 --- a/client/src/lib/hooks/usetodo.js +++ b/client/src/lib/hooks/usetodo.js @@ -2,10 +2,21 @@ import { useSyncExternalStore } from 'react' // https://react.dev/reference/react/useSyncExternalStore +// Global item id let nextId = 0 + +// Global store containing an array of objects let todos = [{ id: nextId++, text: 'Todo #1' }] + +// Internal listeners for each method in todoStore initialized by useSyncExternalStore. +// Needs to call emitChange() to take effect let listeners = [] +/** + * Exportable hook that uses useSyncExternalStore and a global variable to store data. + * Usage: const { todos, addTodo, deleteTodo } = useTodos() + * @returns {Object} { todos, addTodo, deleteTodo } + */ export default function useTodos () { const todos = useSyncExternalStore( todoStore.subscribe, @@ -20,6 +31,12 @@ export default function useTodos () { } } +/** + * The external store with available useSyncExternalStore-required methods: + * subscribe() - Used and initialized internally by useSyncExternalStore + * getSnapshot() - Returns the current (full) snapshot of the global data variable + * getServerSnapshot() - Used in SSR + */ export const todoStore = { addTodo () { todos = [ ...todos, { id: nextId++, text: 'Todo #' + nextId }] From 649ca2d1e7f91f3dd5b22ad409fb6d42cdd90cde Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 16:45:54 +0800 Subject: [PATCH 09/14] chore: Update docs --- client/src/lib/hooks/usetodo.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/lib/hooks/usetodo.js b/client/src/lib/hooks/usetodo.js index 146a1a9..756752a 100644 --- a/client/src/lib/hooks/usetodo.js +++ b/client/src/lib/hooks/usetodo.js @@ -33,7 +33,9 @@ export default function useTodos () { /** * The external store with available useSyncExternalStore-required methods: - * subscribe() - Used and initialized internally by useSyncExternalStore + * subscribe() + * - Used and initialized internally by useSyncExternalStore for subscribing to external store events. + * - This should return a clean-up function like in useEffect() with empty dependency arrays. * getSnapshot() - Returns the current (full) snapshot of the global data variable * getServerSnapshot() - Used in SSR */ From 92b2eface7e088794e8be752e5c6dc543318b2e2 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 16:53:28 +0800 Subject: [PATCH 10/14] chore: Testing page re-renders when loading redux data from deeply-nested components --- client/src/common/ui/card/Card.module.css | 3 ++- client/src/components/redux/index.js | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/client/src/common/ui/card/Card.module.css b/client/src/common/ui/card/Card.module.css index f4d4f10..7f38a9c 100644 --- a/client/src/common/ui/card/Card.module.css +++ b/client/src/common/ui/card/Card.module.css @@ -1,6 +1,7 @@ .card { border: 1px solid; - max-width: 400px; + min-width: 400px; + max-width: 600px; min-height: 100px; border-radius: 16px; padding: 16px; diff --git a/client/src/components/redux/index.js b/client/src/components/redux/index.js index 0d73808..ef03eb2 100644 --- a/client/src/components/redux/index.js +++ b/client/src/components/redux/index.js @@ -13,12 +13,30 @@ function ReduxComponent ({ Redux Toolkit +

Testing rendering data from a redux store inside a deeply-nested component.

+ - +

+ + {/** A deeply-nested component */} + + + + + + + + + + + + + +
From b7b6675d623b2462797f4860a3fe6a8c23d4121f Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 16:55:59 +0800 Subject: [PATCH 11/14] chore: Testing page re-renders when loading global variable data from deeply-nested components using usesyncexternalstore --- .../components/usesyncexternalstore/index.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/client/src/components/usesyncexternalstore/index.js b/client/src/components/usesyncexternalstore/index.js index f0be97d..a4e5c22 100644 --- a/client/src/components/usesyncexternalstore/index.js +++ b/client/src/components/usesyncexternalstore/index.js @@ -13,9 +13,21 @@ function UseSyncExternalStoreComponent ({ UseSyncExternalStoreComponent - + + + + + + + + + + + + + + +

+ @@ -29,11 +40,7 @@ function UseSyncExternalStoreComponent ({ - - -

+
Hello From 37fe6ebe61892410a5898bb5022f0787c4ae4e22 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 17:28:27 +0800 Subject: [PATCH 13/14] chore: Testing page re-renders when loading local state data set by useState from deeply-nested components --- client/src/components/home/items.json | 4 ++ client/src/components/usestate/index.js | 59 ++++++++++++++++++++ client/src/domain/usestate/todolist/index.js | 27 +++++++++ client/src/pages/_app.js | 8 ++- client/src/pages/usestate/index.js | 9 +++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 client/src/components/usestate/index.js create mode 100644 client/src/domain/usestate/todolist/index.js create mode 100644 client/src/pages/usestate/index.js diff --git a/client/src/components/home/items.json b/client/src/components/home/items.json index 6d5b0e2..9deab84 100644 --- a/client/src/components/home/items.json +++ b/client/src/components/home/items.json @@ -6,5 +6,9 @@ { "name": "redux toolkit", "link": "/redux" + }, + { + "name": "useState", + "link": "/usestate" } ] \ No newline at end of file diff --git a/client/src/components/usestate/index.js b/client/src/components/usestate/index.js new file mode 100644 index 0000000..1dccee8 --- /dev/null +++ b/client/src/components/usestate/index.js @@ -0,0 +1,59 @@ +import { useState } from 'react' +import Card from '@/common/ui/card' +import Page from '@/common/layout/page' +import TodoListComponentV3 from '@/domain/usestate/todolist' + +function UseStateComponent () { + const [state, setState] = useState([]) + + const addTodo = () => { + const data = [...state] + data.push({ + id: Math.random().toString(36).substring(2, 8), + text: 'Hello, wooorld!!' + }) + + setState(data) + } + + const deleteTodo = (id) => { + const temp = state.filter(item => item.id !== id) + setState(temp) + } + + return ( + +

+ useState +

+ +

Testing page re-renders and local state set by useState rendering from inside a deeply-nested component.

+
+ + +

+ + + + + + + + + + + + + + +
+ ) +} + +export default UseStateComponent diff --git a/client/src/domain/usestate/todolist/index.js b/client/src/domain/usestate/todolist/index.js new file mode 100644 index 0000000..2da0675 --- /dev/null +++ b/client/src/domain/usestate/todolist/index.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' + +function TodoListComponentV3 ({ + todos, + deleteTodo +}) { + return ( +
    + {(todos).map(((item, index) => ( +
  • + id: {item.id}, {item.text} + + + +
  • + )))} +
+ ) +} + +TodoListComponentV3.propTypes = { + deleteTodo: PropTypes.func +} + +export default TodoListComponentV3 diff --git a/client/src/pages/_app.js b/client/src/pages/_app.js index 4b7ffc0..bf9ec3f 100644 --- a/client/src/pages/_app.js +++ b/client/src/pages/_app.js @@ -4,7 +4,9 @@ import { store } from '@/store/store' import '@/styles/globals.css' export default function App({ Component, pageProps }) { - return - - + return ( + + + + ) } diff --git a/client/src/pages/usestate/index.js b/client/src/pages/usestate/index.js new file mode 100644 index 0000000..c9fa64c --- /dev/null +++ b/client/src/pages/usestate/index.js @@ -0,0 +1,9 @@ +import UseStateComponent from '@/components/usestate' + +function UseStateContainer () { + return ( + + ) +} + +export default UseStateContainer From b87086541d34f441057f9019f120750608493b5a Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 19 Mar 2023 17:42:12 +0800 Subject: [PATCH 14/14] chore: Display todo list state data from an inner component --- client/src/components/usestate/index.js | 26 +++++++++++ client/src/domain/usestate/todolist/index.js | 1 + .../src/domain/usestate/todolistfull/index.js | 44 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 client/src/domain/usestate/todolistfull/index.js diff --git a/client/src/components/usestate/index.js b/client/src/components/usestate/index.js index 1dccee8..9b7c353 100644 --- a/client/src/components/usestate/index.js +++ b/client/src/components/usestate/index.js @@ -1,7 +1,9 @@ import { useState } from 'react' import Card from '@/common/ui/card' import Page from '@/common/layout/page' + import TodoListComponentV3 from '@/domain/usestate/todolist' +import TodoListComponentFull from '@/domain/usestate/todolistfull' function UseStateComponent () { const [state, setState] = useState([]) @@ -35,6 +37,10 @@ function UseStateComponent () {

+

ToDo list state passed from props

+
+ + {/** Renders a list of ToDo items passed from props */} @@ -52,6 +58,26 @@ function UseStateComponent () { + +

+ +

ToDo list state isolated on an inner component

+
+ + {/** Renders a list of ToDo items with all local state inside the TodoListComponentFull component */} + + + + + + + + + + + + + ) } diff --git a/client/src/domain/usestate/todolist/index.js b/client/src/domain/usestate/todolist/index.js index 2da0675..29949ee 100644 --- a/client/src/domain/usestate/todolist/index.js +++ b/client/src/domain/usestate/todolist/index.js @@ -21,6 +21,7 @@ function TodoListComponentV3 ({ } TodoListComponentV3.propTypes = { + todos: PropTypes.array, deleteTodo: PropTypes.func } diff --git a/client/src/domain/usestate/todolistfull/index.js b/client/src/domain/usestate/todolistfull/index.js new file mode 100644 index 0000000..aad705a --- /dev/null +++ b/client/src/domain/usestate/todolistfull/index.js @@ -0,0 +1,44 @@ +import { useState } from 'react' + +function TodoListComponentFull () { + const [todos, setState] = useState([]) + + const addTodo = () => { + const data = [...todos] + data.push({ + id: Math.random().toString(36).substring(2, 8), + text: 'Hi, wooorld!!' + }) + + setState(data) + } + + const deleteTodo = (id) => { + const temp = todos.filter(item => item.id !== id) + setState(temp) + } + + return ( + <> + +

+ +
    + {(todos).map(((item, index) => ( +
  • + id: {item.id}, {item.text} + + + +
  • + )))} +
+ + ) +} + +export default TodoListComponentFull