Skip to content

Commit 1ee39eb

Browse files
committed
[IMPL] SwitchCase Component
1 parent 19e8452 commit 1ee39eb

13 files changed

Lines changed: 158 additions & 18 deletions

File tree

apps/react-tools-demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"dev": "node scripts/generateMarkdown.js && node scripts/generateRouter.js && node scripts/generateMainLayout.js && vite",
8-
"dev:no-docs": "node scripts/generateRouter.js && node scripts/generateMainLayout.js && vite",
8+
"dev:no-docs": "node scripts/generateRouter.js no-docs && node scripts/generateMainLayout.js && vite",
99
"build": "node scripts/generateMarkdown.js && node scripts/generateRouter.js && node scripts/generateMainLayout.js && tsc && vite build",
1010
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1111
"preview": "vite preview"

apps/react-tools-demo/scripts/generateRouter.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ async function generateImport(router) {
8181
"import ComponentLayout from '../layout/ComponentLayout';",
8282
"import { Spinner } from '../layout/Spinner';"
8383
]);
84-
await generateImportMarkdown(router);
84+
process.argv && process.argv[2] === "no-docs" ? undefined : await generateImportMarkdown(router);
8585
await generateImportDemoComponent(router);
8686
}
8787

@@ -113,7 +113,7 @@ function createHooksRoutes(router, stateFiles, lifecycleFiles, performanceFiles,
113113
router.add(' {');
114114
router.add(` path: "${f}",`);
115115
router.add(' element: <Suspense fallback={<Spinner/>}>');
116-
router.add(` <ComponentLayout markdown={${f + "MD"}} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
116+
router.add(` <ComponentLayout ${router.value.includes(`import ${f}MD from`) ? `markdown={${f}MD}` : ""} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
117117
router.add(' </Suspense>');
118118
router.add(' },');
119119
})
@@ -133,7 +133,7 @@ function createHooksRoutes(router, stateFiles, lifecycleFiles, performanceFiles,
133133
router.add(' {');
134134
router.add(` path: "${f}",`);
135135
router.add(' element: <Suspense fallback={<Spinner/>}>');
136-
router.add(` <ComponentLayout markdown={${f + "MD"}} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
136+
router.add(` <ComponentLayout ${router.value.includes(`import ${f}MD from`) ? `markdown={${f}MD}` : ""} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
137137
router.add(' </Suspense>');
138138
router.add(' },');
139139
})
@@ -153,7 +153,7 @@ function createHooksRoutes(router, stateFiles, lifecycleFiles, performanceFiles,
153153
router.add(' {');
154154
router.add(` path: "${f}",`);
155155
router.add(' element: <Suspense fallback={<Spinner/>}>');
156-
router.add(` <ComponentLayout markdown={${f + "MD"}} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
156+
router.add(` <ComponentLayout ${router.value.includes(`import ${f}MD from`) ? `markdown={${f}MD}` : ""} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
157157
router.add(' </Suspense>');
158158
router.add(' },');
159159
})
@@ -173,7 +173,7 @@ function createHooksRoutes(router, stateFiles, lifecycleFiles, performanceFiles,
173173
router.add(' {');
174174
router.add(` path: "${f}",`);
175175
router.add(' element: <Suspense fallback={<Spinner/>}>');
176-
router.add(` <ComponentLayout markdown={${f + "MD"}} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
176+
router.add(` <ComponentLayout ${router.value.includes(`import ${f}MD from`) ? `markdown={${f}MD}` : ""} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
177177
router.add(' </Suspense>');
178178
router.add(' },');
179179
})
@@ -193,7 +193,7 @@ function createHooksRoutes(router, stateFiles, lifecycleFiles, performanceFiles,
193193
router.add(' {');
194194
router.add(` path: "${f}",`);
195195
router.add(' element: <Suspense fallback={<Spinner/>}>');
196-
router.add(` <ComponentLayout markdown={${f + "MD"}} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
196+
router.add(` <ComponentLayout ${router.value.includes(`import ${f}MD from`) ? `markdown={${f}MD}` : ""} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
197197
router.add(' </Suspense>');
198198
router.add(' },');
199199
})
@@ -216,7 +216,7 @@ function createRoutes(router, componentsFiles, parentRoot) {
216216
router.add(' {');
217217
router.add(` path: "${f}",`);
218218
router.add(' element: <Suspense fallback={<Spinner/>}>');
219-
router.add(` <ComponentLayout markdown={${f + "MD"}} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
219+
router.add(` <ComponentLayout ${router.value.includes(`import ${f}MD from`) ? `markdown={${f}MD}` : ""} ${router.value.includes(`const ${f.charAt(0).toUpperCase() + f.substring(1)} = lazy`) ? `component={<${f.charAt(0).toUpperCase() + f.substring(1)}/>}` : ""}/>`);
220220
router.add(' </Suspense>');
221221
router.add(' },');
222222
})

apps/react-tools-demo/src/layout/MainLayout.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,17 @@ export default function MainLayout() {
12741274
>
12751275
Show
12761276
</Link>
1277+
<Link
1278+
className={pathname === "/components/SwitchCase" ? 'active' : ''}
1279+
ref={node => linksRef.current["SwitchCase"] = node}
1280+
to="/components/SwitchCase"
1281+
onClick={() => {
1282+
containerRef.current?.scrollTo(0, 0);
1283+
window.innerWidth < 1190 && closeNav();
1284+
}}
1285+
>
1286+
SwitchCase
1287+
</Link>
12771288
<p className="sub-type">Utils</p>
12781289
<Link
12791290
className={pathname === "/utils/alphanumericCompare" ? 'active' : ''}

apps/react-tools-demo/src/markdown/Lazy.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ Component Wrapper to lazy loading a Component. [See demo](https://nDriaDev.io/re
77
export default function LC() {
88
return (
99
<Lazy
10-
factory={() => import('./LazyComponent').then(res => new Promise<{ [k: string]: ComponentType<unknown> }>(resolve => setTimeout(()=>resolve(res), 5000)))}
10+
factory={() => import('./LazyComponent').then(async res => {
11+
await new Promise(resolve => setTimeout(resolve, 5000));
12+
return res;
13+
})}
1114
fallback={<p>Loading component...</p>}
1215
/>
1316
);

apps/react-tools-demo/src/markdown/Show.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ export default function ShowComponent() {
2222
## API
2323

2424
```tsx
25-
Show({ when, fallback, children }: PropsWithChildren<{ when: boolean, fallback?: ReactNode }>)
25+
Show<T extends unknown>({ when, fallback, children }: PropsWithChildren<{ when: T|boolean|undefined|null, fallback?: ReactNode }>)
2626
```
2727

2828
> ### Params
2929
>
3030
> - __object__: _PropsWithChildren<{when: boolean, fallback?: ReactNode}>_
31-
> - __object.when__: _boolean_
31+
> - __object.when__: _T|boolean|undefined|null_
3232
boolean indicating if to show _children_ or _fallback_/_null_.
3333
> - __object.fallback?__: _ReactNode_
3434
optional element to render when _when_ prop is false.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SwitchCase
2+
It works like switch-case construct. It useful for when there are more than 2 mutual exclusive conditions.
3+
4+
## Usage
5+
6+
```tsx
7+
export default function SM() {
8+
const valuesCounter = useRef([1, 2, 3]);
9+
const [indexCounter, setIndexCounter] = useState(0);
10+
11+
useEffect(() => {
12+
const id = setInterval(() => setIndexCounter(i => i%4+1 === 4 ? 0 : i%4+1), 2000);
13+
return () => clearInterval(id);
14+
}, []);
15+
16+
return (<div style={{textAlign: "left", display: "flex", flexDirection: "column", alignItems: "center"}}>
17+
<p style={{width: 230}}>Counter values: {JSON.stringify(valuesCounter.current, null, 2)}</p>
18+
<p style={{width: 230}}>Counter index: {indexCounter}</p>
19+
<SwitchCase.Switch fallback={<p style={{color: "darkorange", width: 230}}>Counter value unsetted.</p>}>
20+
<SwitchCase.Case when={indexCounter === 0}>
21+
<p style={{color: "darkturquoise", width: 230, fontWeight: 800}}>Counter value is 1.</p>
22+
</SwitchCase.Case>
23+
<SwitchCase.Case when={indexCounter === 1}>
24+
<p style={{color: "darkkhaki", width: 230, fontWeight: 800}}>Counter value is 2.</p>
25+
</SwitchCase.Case>
26+
<SwitchCase.Case when={indexCounter === 2}>
27+
<p style={{color: "darkcyan", width: 230, fontWeight: 800}}>Counter value is 3.</p>
28+
</SwitchCase.Case>
29+
</SwitchCase.Switch>
30+
</div>);
31+
}
32+
```
33+
34+
> The component has an array of numbers and a variable state number used like index of array. Every 2 seconds index changes value. It uses __SwitchCase__ component to render different p element with a text indicating the value of array with the current index.
35+
36+
37+
## API
38+
39+
```tsx
40+
SwitchCaseSwitch = ({ children, fallback }: PropsWithChildren<{ fallback?: ReactNode }>)
41+
```
42+
43+
> ### Params
44+
>
45+
> - __object.fallback?__: _ReactNode_
46+
optional element to render when _when_ prop is false.
47+
> - __object.children?__: _PropsWithChildren<any>["children"]_
48+
__Case__ components.
49+
>
50+
51+
> ### Returns
52+
>
53+
> __element__: _JSX.Element|null_
54+
> __Case__ Component has these properties:
55+
> - _children_: element to render.
56+
> - _when_: condition that if true return _children_, otherwise null.
57+
>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useEffect, useRef, useState } from "react";
2+
import { SwitchCase } from "../../../../../../packages/react-tools/src";
3+
4+
/**
5+
The component has an array of numbers and a variable state number used like index of array. Every 2 seconds index changes value. It uses __SwitchCase__ component to render different p element with a text indicating the value of array with the current index.
6+
*/
7+
export default function SM() {
8+
const valuesCounter = useRef([1, 2, 3]);
9+
const [indexCounter, setIndexCounter] = useState(0);
10+
11+
useEffect(() => {
12+
const id = setInterval(() => setIndexCounter(i => i%4+1 === 4 ? 0 : i%4+1), 2000);
13+
return () => clearInterval(id);
14+
}, []);
15+
16+
return (<div style={{textAlign: "left", display: "flex", flexDirection: "column", alignItems: "center"}}>
17+
<p style={{width: 230}}>Counter values: {JSON.stringify(valuesCounter.current, null, 2)}</p>
18+
<p style={{width: 230}}>Counter index: {indexCounter}</p>
19+
<SwitchCase.Switch fallback={<p style={{color: "darkorange", width: 230}}>Counter value unsetted.</p>}>
20+
<SwitchCase.Case when={indexCounter === 0}>
21+
<p style={{color: "darkturquoise", width: 230, fontWeight: 800}}>Counter value is 1.</p>
22+
</SwitchCase.Case>
23+
<SwitchCase.Case when={indexCounter === 1}>
24+
<p style={{color: "darkkhaki", width: 230, fontWeight: 800}}>Counter value is 2.</p>
25+
</SwitchCase.Case>
26+
<SwitchCase.Case when={indexCounter === 2}>
27+
<p style={{color: "darkcyan", width: 230, fontWeight: 800}}>Counter value is 3.</p>
28+
</SwitchCase.Case>
29+
</SwitchCase.Switch>
30+
</div>);
31+
}

apps/react-tools-demo/src/router/Router.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ComponentLayout from '../layout/ComponentLayout';
55
import { Spinner } from '../layout/Spinner';
66
import LazyMD from "../markdown/Lazy.md?raw"
77
import ShowMD from "../markdown/Show.md?raw"
8+
import SwitchCaseMD from "../markdown/SwitchCase.md?raw"
89
import alphanumericCompareMD from "../markdown/alphanumericCompare.md?raw"
910
import changeStringCaseMD from "../markdown/changeStringCase.md?raw"
1011
import createPubSubStoreMD from "../markdown/createPubSubStore.md?raw"
@@ -134,6 +135,7 @@ import useWebWorkerMD from "../markdown/useWebWorker.md?raw"
134135
import useWebWorkerFnMD from "../markdown/useWebWorkerFn.md?raw"
135136
const Lazy = lazy((() => import('../pages/components/lazy/Lazy').then(module => ({default: "default" in module ? module["default"] : module["Lazy"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
136137
const Show = lazy((() => import('../pages/components/show/Show').then(module => ({default: "default" in module ? module["default"] : module["Show"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
138+
const SwitchCase = lazy((() => import('../pages/components/switchCase/SwitchCase').then(module => ({default: "default" in module ? module["default"] : module["SwitchCase"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
137139
import HomeWrapper from '../pages/home/HomeWrapper'
138140
const UseActiveElement = lazy((() => import('../pages/hooks/api-dom/useActiveElement/UseActiveElement').then(module => ({default: "default" in module ? module["default"] : module["UseActiveElement"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
139141
const UseAnimation = lazy((() => import('../pages/hooks/api-dom/useAnimation/UseAnimation').then(module => ({default: "default" in module ? module["default"] : module["UseAnimation"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
@@ -992,6 +994,12 @@ function Router() {
992994
<ComponentLayout markdown={ShowMD} component={<Show/>}/>
993995
</Suspense>
994996
},
997+
{
998+
path: "SwitchCase",
999+
element: <Suspense fallback={<Spinner/>}>
1000+
<ComponentLayout markdown={SwitchCaseMD} component={<SwitchCase/>}/>
1001+
</Suspense>
1002+
},
9951003
]
9961004
},
9971005
{

packages/react-tools/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,13 @@
147147
- [x] removePropertiesFromArrayObjects
148148

149149
- __COMPONENT__
150-
- [ ] Show component to render or not a component by a condition. Props: when, fallback, keyed. Keyed is a boolean and needs to avoid rerenders children when it is a function.
151-
- [ ] Switch and Match components with fallback and when props
150+
- [x] Show
151+
- [x] SwitchCase
152152
- [ ] For/Each: A referentially keyed loop with efficient updating of only changed items. The callback takes the current item as the first argument (ref https://javascript.plainenglish.io/react-each-and-of-pattern-b00aa4305089)
153153
- [ ] Index: Non-keyed list iteration (rendered nodes are keyed to an array index). This is useful when there is no conceptual key, like if the data consists of primitives and it is the index that is fixed rather than the value.
154154
- [ ] RestrictedRoute (maybe)
155155
- [ ] ErrorBoundary (?? error event listener)
156-
- [ ] LazyComponent: Async component loading by path and fallback for suspense
156+
- [x] Lazy
157157
- [ ] Suspense: Suspence compontent react-like for async component (or polyfill)
158158
- [ ] Dynamic: This component lets you insert an arbitrary Component or tag and passes the props through to it.
159159
- [ ] ImageOpt (???)

packages/react-tools/src/components/Show.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
12
import { PropsWithChildren, ReactNode } from "react";
23

34
/**
45
* **`Show`**: Generic component used to conditional render part of the view: it renders _children_ when the _when_ prop is truthy, otherwise the _fallback_ prop, if it is present, or null. [See demo](https://nDriaDev.io/react-tools/#/components/Show)
56
* @param {PropsWithChildren<{when: boolean, fallback?: ReactNode}>} object
6-
* @param {boolean} object.when - boolean indicating if to show _children_ or _fallback_/_null_.
7+
* @param {T|boolean|undefined|null} object.when - boolean indicating if to show _children_ or _fallback_/_null_.
78
* @param {ReactNode} [object.fallback] - optional element to render when _when_ prop is false.
89
* @param {PropsWithChildren<any>["children"]} [object.children] - optional element to render when _when_ prop is true.
910
* @returns {JSX.Element|null} element - the element rendered or null.
1011
*/
11-
export const Show = ({ when, fallback, children }: PropsWithChildren<{ when: boolean, fallback?: ReactNode }>) => {
12+
export const Show = <T extends unknown>({ when, fallback, children }: PropsWithChildren<{ when: T|boolean|undefined|null, fallback?: ReactNode }>) => {
1213
if (!when) {
1314
return fallback ? <>{fallback}</> : null;
1415
}

0 commit comments

Comments
 (0)