Skip to content

Commit 3dbacab

Browse files
committed
[IMPL] For Component
1 parent 1ee39eb commit 3dbacab

12 files changed

Lines changed: 175 additions & 6 deletions

File tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,17 @@ export default function MainLayout() {
12521252
useWebWorkerFn
12531253
</Link>
12541254
<p className="sub-type">Components</p>
1255+
<Link
1256+
className={pathname === "/components/For" ? 'active' : ''}
1257+
ref={node => linksRef.current["For"] = node}
1258+
to="/components/For"
1259+
onClick={() => {
1260+
containerRef.current?.scrollTo(0, 0);
1261+
window.innerWidth < 1190 && closeNav();
1262+
}}
1263+
>
1264+
For
1265+
</Link>
12551266
<Link
12561267
className={pathname === "/components/Lazy" ? 'active' : ''}
12571268
ref={node => linksRef.current["Lazy"] = node}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# For
2+
Component to optimize the rendering of a list of elements without need to specify a key value for all elements, and other options. [See demo](https://nDriaDev.io/react-tools/#/components/For)
3+
4+
## Usage
5+
6+
```tsx
7+
export default function ForComponent() {
8+
const arr = useRef([
9+
{ id: "1", firstName: "Jhon", lastName: "Doe" },
10+
{ id: "2", firstName: "Jona", lastName: "Doe" },
11+
{ id: "3", firstName: "Jhonney", lastName: "Doe" }
12+
]);
13+
const [, update] = useReducer(t => !t, false);
14+
15+
const body = useCallback<(item: { id: string, firstName: string, lastName: string }, index: number|string) => JSX.Element>((item, index) => {
16+
console.log("body render");
17+
return <p>{index}: {item.firstName} - {item.lastName}</p>
18+
}, []);
19+
20+
useEffect(() => {
21+
const id = setInterval(() => update(), 1000);
22+
return () => clearInterval(id)
23+
}, [])
24+
25+
return <>
26+
<For
27+
elementKey="id"
28+
of={arr.current}
29+
>
30+
{body}
31+
</For>
32+
</>
33+
}
34+
```
35+
36+
> The component uses _For_ component to render a p element returned from _body_ memoized function for all objects assigned to _arr_ ref variable. It also specified __id__ property as _elementKey_ prop. A setInverval is executed on mount that on every second force component to rerender. If you open dev tools, you can see that _body_ function logs only three times at first, one for each element of _arr_ variable
37+
38+
39+
## API
40+
41+
```tsx
42+
Formemo(<T extends unknown>({ of, children, filter, map, sort, elementKey, fallback }: ForProps<T>)
43+
```
44+
45+
> ### Params
46+
>
47+
> - __props__: _ForProps<T>_
48+
component properties object.
49+
> - __props.of__: _T[]_
50+
array of elements.
51+
> - __props.elementKey?__: _T extends Record<string,unknown> ? keyof T : never_
52+
a key of array elements if elements are object.
53+
> - __props.children__: _(item: T, index: T extends Record<string,unknown> ? number | T[keyof T] : number) => ReactNode_
54+
it's a function that takes the current item as first argument and optionally a second argument that is number if element of array aren't object, otherwise it can be a number or the value of the element key specified in the _elementKey_ prop if it is preset.
55+
> - __props.fallback?__: _ReactNode_
56+
optional element to render when _of_ prop is an empty array.
57+
> - __props.filter?__: _<S extends T>(val: T, index: number, arr: T[]) => val is S_
58+
callback executed to filter _of_ elements.
59+
> - __props.map?__: _<U extends T>(val: T, index: number, arr: T[]) => U_
60+
callback executed to map _of_ elements.
61+
> - __props.sort?__: _(a: T, b: T) => number_
62+
callback executed to sort _of_ elements.
63+
>
64+
65+
> ### Returns
66+
>
67+
> __result__: elements list, rendered from _of_ prop or _fallback_.
68+
> - _JSX.Element_
69+
>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# SwitchCase
2-
It works like switch-case construct. It useful for when there are more than 2 mutual exclusive conditions.
2+
It works like switch-case construct. It useful for when there are more than 2 mutual exclusive conditions. [See demo](https://nDriaDev.io/react-tools/#/components/SwitchCase)
33

44
## Usage
55

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useCallback, useEffect, useReducer, useRef } from "react";
2+
import { For } from "../../../../../../packages/react-tools/src";
3+
4+
/**
5+
The component uses _For_ component to render a p element returned from _body_ memoized function for all objects assigned to _arr_ ref variable. It also specified __id__ property as _elementKey_ prop. A setInverval is executed on mount that on every second force component to rerender. If you open dev tools, you can see that _body_ function logs only three times at first, one for each element of _arr_ variable
6+
*/
7+
export default function ForComponent() {
8+
const arr = useRef([
9+
{ id: "1", firstName: "Jhon", lastName: "Doe" },
10+
{ id: "2", firstName: "Jona", lastName: "Doe" },
11+
{ id: "3", firstName: "Jhonney", lastName: "Doe" }
12+
]);
13+
const [, update] = useReducer(t => !t, false);
14+
15+
const body = useCallback<(item: { id: string, firstName: string, lastName: string }, index: number|string) => JSX.Element>((item, index) => {
16+
console.log("body render");
17+
return <p>{index}: {item.firstName} - {item.lastName}</p>
18+
}, []);
19+
20+
useEffect(() => {
21+
const id = setInterval(() => update(), 1000);
22+
return () => clearInterval(id)
23+
}, [])
24+
25+
return <>
26+
<For
27+
elementKey="id"
28+
of={arr.current}
29+
>
30+
{body}
31+
</For>
32+
</>
33+
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { RouterProvider, createHashRouter, Outlet, Navigate } from 'react-router
33
import MainLayout from '../layout/MainLayout';
44
import ComponentLayout from '../layout/ComponentLayout';
55
import { Spinner } from '../layout/Spinner';
6+
import ForMD from "../markdown/For.md?raw"
67
import LazyMD from "../markdown/Lazy.md?raw"
78
import ShowMD from "../markdown/Show.md?raw"
89
import SwitchCaseMD from "../markdown/SwitchCase.md?raw"
@@ -133,6 +134,7 @@ import useVisibleMD from "../markdown/useVisible.md?raw"
133134
import useWebSocketMD from "../markdown/useWebSocket.md?raw"
134135
import useWebWorkerMD from "../markdown/useWebWorker.md?raw"
135136
import useWebWorkerFnMD from "../markdown/useWebWorkerFn.md?raw"
137+
const For = lazy((() => import('../pages/components/for/For').then(module => ({default: "default" in module ? module["default"] : module["For"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
136138
const Lazy = lazy((() => import('../pages/components/lazy/Lazy').then(module => ({default: "default" in module ? module["default"] : module["Lazy"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
137139
const Show = lazy((() => import('../pages/components/show/Show').then(module => ({default: "default" in module ? module["default"] : module["Show"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
138140
const SwitchCase = lazy((() => import('../pages/components/switchCase/SwitchCase').then(module => ({default: "default" in module ? module["default"] : module["SwitchCase"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
@@ -980,8 +982,14 @@ function Router() {
980982
children: [
981983
{
982984
index: true,
983-
element: <Navigate to={"/components/Lazy"} replace/>,
985+
element: <Navigate to={"/components/For"} replace/>,
984986
},
987+
{
988+
path: "For",
989+
element: <Suspense fallback={<Spinner/>}>
990+
<ComponentLayout markdown={ForMD} component={<For/>}/>
991+
</Suspense>
992+
},
985993
{
986994
path: "Lazy",
987995
element: <Suspense fallback={<Spinner/>}>

packages/react-tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@
149149
- __COMPONENT__
150150
- [x] Show
151151
- [x] SwitchCase
152-
- [ ] 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)
152+
- [x] For
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)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
2+
import { Children, memo } from "react";
3+
import { ForProps } from "../models";
4+
5+
/**
6+
* **`For`**: Component to optimize the rendering of a list of elements without need to specify a key value for all elements, and other options. [See demo](https://nDriaDev.io/react-tools/#/components/For)
7+
* @param {ForProps<T>} props - component properties object.
8+
* @param {T[]} props.of - array of elements.
9+
* @param {T extends Record<string,unknown> ? keyof T : never} [props.elementKey] - a key of array elements if elements are object.
10+
* @param {(item: T, index: T extends Record<string,unknown> ? number | T[keyof T] : number) => ReactNode} props.children - it's a function that takes the current item as first argument and optionally a second argument that is number if element of array aren't object, otherwise it can be a number or the value of the element key specified in the _elementKey_ prop if it is preset.
11+
* @param {ReactNode} [props.fallback] - optional element to render when _of_ prop is an empty array.
12+
* @param {<S extends T>(val: T, index: number, arr: T[]) => val is S} [props.filter] - callback executed to filter _of_ elements.
13+
* @param {<U extends T>(val: T, index: number, arr: T[]) => U} [props.map] - callback executed to map _of_ elements.
14+
* @param {(a: T, b: T) => number} [props.sort] - callback executed to sort _of_ elements.
15+
* @returns {JSX.Element} result - elements list, rendered from _of_ prop or _fallback_.
16+
*/
17+
export const For = memo(<T extends unknown>({ of, children, filter, map, sort, elementKey, fallback }: ForProps<T>) => {
18+
if (of.length === 0) {
19+
return <>{fallback}</>
20+
}
21+
let arr: T[] = sort ? of.sort(sort) : of;
22+
arr = filter ? arr.filter(filter) : arr;
23+
arr = map ? arr.map(map) : arr;
24+
return (
25+
<>
26+
{Children.toArray(
27+
arr.map((item, index) => (
28+
children(item, (elementKey ? item[elementKey] : index) as T extends Record<string, unknown> ? number | T[keyof T] : number)
29+
))
30+
)}
31+
</>
32+
);
33+
}) as <T extends unknown>(props: ForProps<T>) => JSX.Element;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const Case = ({ children, when }: PropsWithChildren<{ when: boolean | undefined
88
}
99

1010
/**
11-
* **`SwitchCase`**: It works like switch-case construct. It useful for when there are more than 2 mutual exclusive conditions.
11+
* **`SwitchCase`**: It works like switch-case construct. It useful for when there are more than 2 mutual exclusive conditions. [See demo](https://nDriaDev.io/react-tools/#/components/SwitchCase)
1212
* @param {ReactNode} [object.fallback] - optional element to render when _when_ prop is false.
1313
* @param {PropsWithChildren<any>["children"]} [object.children] - __Case__ components.
1414
* @returns {JSX.Element|null} element - __Case__ component with _when_ prop value __`true`__ or _fallback_ prop.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { Lazy } from './Lazy';
22
export { Show } from './Show';
3-
export { SwitchCase } from './SwitchCase';
3+
export { SwitchCase } from './SwitchCase';
4+
export { For } from './For';

packages/react-tools/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type {
2525
ExtractHead,
2626
ExtractMiddle,
2727
ExtractTail,
28+
ForProps,
2829
GeoLocationObject,
2930
HTMLAttributes,
3031
HTMLMediaControls,
@@ -212,8 +213,9 @@ export {
212213
} from './hooks'
213214

214215
export {
215-
Show,
216+
For,
216217
Lazy,
218+
Show,
217219
SwitchCase
218220
} from './components'
219221

0 commit comments

Comments
 (0)