Skip to content

Commit d9bb133

Browse files
committed
Fix issue with descriptor indexes when children change
1 parent 0a640ad commit d9bb133

10 files changed

Lines changed: 120 additions & 50 deletions

example/src/components/features/color-data-list/color-data-list-row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function ColorDataListRow({colors}: Omit<ColorDataListRowItem, "id">) {
2626
return (
2727
<ReactDataList.Row
2828
id={id}
29-
item={React.useMemo(() => ({id, colors}), [id, colors])}
29+
item={React.useMemo(() => ({id, colors}), [colors])}
3030
render={renderColorDataList}
3131
recyclerType="color-data-list"
3232
/>

example/src/components/features/list-header/list-header-data-list-row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function ListHeaderDataListRow({title}: Omit<ListHeaderItem, "id">) {
2828
return (
2929
<ReactDataList.Row
3030
id={id}
31-
item={React.useMemo(() => ({id, title}), [id, title])}
31+
item={React.useMemo(() => ({id, title}), [title])}
3232
render={renderListHeader}
3333
recyclerType="list-header"
3434
/>

example/src/components/features/list-item/list-item-data-list-row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function ListItemDataListRow({name, color}: Omit<ListItemItem, "id">) {
3434
return (
3535
<ReactDataList.Row
3636
id={id}
37-
item={React.useMemo(() => ({id, name, color}), [id, name, color])}
37+
item={React.useMemo(() => ({id, name, color}), [name, color])}
3838
render={renderListItem}
3939
recyclerType="list-item"
4040
/>

src/data-list-descriptor-context.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {createDescendantContext} from "@attio/react-descendants"
55
import type {DataListDescriptor, DataListDescriptorDescendant} from "./data-list-types"
66

77
export interface DataListDescriptorContextProviderContext {
8-
attachDescriptors: (index: number, descriptors: Array<DataListDescriptor>) => () => void
8+
attachDescriptors: (id: string, descriptors: Array<DataListDescriptor>) => () => void
9+
markForIndex: (id: string, index: number) => void
910
}
1011

1112
const DataListDescriptorContext = React.createContext<

src/data-list-descriptors.tsx

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as React from "react"
22

33
import type {
4+
DataListAllDescriptors,
45
DataListDescriptor,
5-
DataListDescriptors as DataListDescriptorsType,
6+
DataListDescriptorIndexes,
7+
DescriptorId,
68
} from "./data-list-types"
79
import {
810
DataListDescriptorContextProvider,
@@ -13,46 +15,70 @@ import {
1315
export interface DataListDescriptorsProps<TRenderItem>
1416
extends Omit<
1517
DataListDescriptorContextProviderContext,
16-
"children" | "attachDescriptors" | "updateDescriptorIndex"
18+
"children" | "attachDescriptors" | "markForIndex"
1719
> {
18-
descriptors: React.ReactNode
19-
setDescriptors: React.Dispatch<React.SetStateAction<DataListDescriptorsType<TRenderItem>>>
20+
rowChildren: React.ReactNode
21+
setAllDescriptors: React.Dispatch<React.SetStateAction<DataListAllDescriptors<TRenderItem>>>
22+
setDescriptorIndexes: React.Dispatch<React.SetStateAction<DataListDescriptorIndexes>>
2023
}
2124

2225
export function DataListDescriptors<TRenderItem>({
23-
descriptors,
24-
setDescriptors,
26+
rowChildren,
27+
setAllDescriptors,
28+
setDescriptorIndexes: outerSetDescriptorIndexes,
2529
}: DataListDescriptorsProps<TRenderItem>) {
2630
const attachDescriptors: DataListDescriptorContextProviderContext["attachDescriptors"] =
2731
React.useCallback(
28-
(index: number, additionalDescriptors: Array<DataListDescriptor<TRenderItem>>) => {
29-
setDescriptors((prevDescriptors) => {
30-
const newDescriptors = new Map(prevDescriptors)
31-
newDescriptors.set(index, additionalDescriptors)
32+
(id: DescriptorId, rowDescriptors: Array<DataListDescriptor<TRenderItem>>) => {
33+
setAllDescriptors((prevAllDescriptors) => {
34+
const newDescriptors = new Map(prevAllDescriptors)
35+
newDescriptors.set(id, rowDescriptors)
3236
return newDescriptors
3337
})
3438

35-
// console.log(
36-
// "attachDescriptors",
37-
// index,
38-
// additionalDescriptors.map((v) => v.item)
39-
// )
40-
4139
return () => {
42-
setDescriptors((prevDescriptors) => {
43-
const newDescriptors = new Map(prevDescriptors)
44-
newDescriptors.delete(index)
40+
setAllDescriptors((prevAllDescriptors) => {
41+
const newDescriptors = new Map(prevAllDescriptors)
42+
newDescriptors.delete(id)
4543
return newDescriptors
4644
})
4745
}
4846
},
49-
[setDescriptors]
47+
[setAllDescriptors]
48+
)
49+
50+
const markForIndex: DataListDescriptorContextProviderContext["markForIndex"] =
51+
React.useCallback(
52+
(id: DescriptorId, index: number) => {
53+
outerSetDescriptorIndexes((prevDescriptorIndexes) => {
54+
const newDescriptorsIndex = new Map(prevDescriptorIndexes)
55+
newDescriptorsIndex.set(index, id)
56+
return newDescriptorsIndex
57+
})
58+
59+
return () => {
60+
outerSetDescriptorIndexes((prevDescriptorIndexes) => {
61+
const currentIdAtIndex = prevDescriptorIndexes.get(index)
62+
63+
// Already used
64+
if (currentIdAtIndex !== id) return prevDescriptorIndexes
65+
66+
const newDescriptorsIndex = new Map(prevDescriptorIndexes)
67+
newDescriptorsIndex.delete(index)
68+
return newDescriptorsIndex
69+
})
70+
}
71+
},
72+
[outerSetDescriptorIndexes]
5073
)
5174

5275
return (
53-
<DataListDescriptorContextProvider attachDescriptors={attachDescriptors}>
76+
<DataListDescriptorContextProvider
77+
attachDescriptors={attachDescriptors}
78+
markForIndex={markForIndex}
79+
>
5480
<DataListDescriptorDescendantContextProvider>
55-
{descriptors}
81+
{rowChildren}
5682
</DataListDescriptorDescendantContextProvider>
5783
</DataListDescriptorContextProvider>
5884
)

src/data-list-master-renderer.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React, {useDeferredValue} from "react"
22

33
import type {
4+
DataListAllDescriptors,
45
DataListDescriptor,
5-
DataListDescriptors,
6+
DataListDescriptorIndexes,
67
DataListRenderer,
7-
DataListRendererProps as DataListRendererPropsType,
8+
DataListRendererProps,
89
DataListRendererRootRenderItem,
910
DataListRendererRootRenderItemArgs,
1011
DataListRenderListItemInfo,
@@ -13,31 +14,37 @@ import {DataListRendererContextProvider} from "./data-list-renderer-context"
1314
import {cancelFrame, requestFrame} from "./utils/request-frame"
1415

1516
export interface DataListMasterRendererProps<TRenderItem>
16-
extends Omit<DataListRendererPropsType<TRenderItem>, "data" | "rootRenderItem" | "getItemId"> {
17+
extends Omit<DataListRendererProps<TRenderItem>, "data" | "rootRenderItem" | "getItemId"> {
1718
renderer: DataListRenderer<TRenderItem> | ReturnType<DataListRenderer<TRenderItem>>
18-
descriptors: DataListDescriptors<TRenderItem>
19+
allDescriptors: DataListAllDescriptors<TRenderItem>
20+
descriptorIndexes: DataListDescriptorIndexes
1921
}
2022

2123
export function DataListMasterRenderer<TRenderItem>({
2224
renderer,
23-
descriptors,
25+
allDescriptors,
26+
descriptorIndexes,
2427
...rest
2528
}: DataListMasterRendererProps<TRenderItem>) {
2629
const newData = React.useMemo((): Array<
2730
DataListRendererRootRenderItemArgs<TRenderItem>["item"]
2831
> => {
29-
const sortedDescriptors = [...descriptors.entries()].sort((a, b) => a[0] - b[0])
32+
const sortedDescriptorIds = [...descriptorIndexes.entries()].sort((a, b) => a[0] - b[0])
3033

31-
return sortedDescriptors.flatMap(([index, descriptors]) =>
32-
descriptors.map((d: DataListDescriptor<TRenderItem>) => ({
34+
return sortedDescriptorIds.flatMap(([index, descriptorId]) => {
35+
const descriptorsForId = allDescriptors.get(descriptorId)
36+
37+
if (!descriptorsForId) return []
38+
39+
return descriptorsForId.map((d: DataListDescriptor<TRenderItem>) => ({
3340
descriptor: {
3441
...d,
3542
id: [index, ...(Array.isArray(d.id) ? d.id : [d.id])],
3643
},
3744
descriptorSourceIndex: index,
3845
}))
39-
)
40-
}, [descriptors])
46+
})
47+
}, [allDescriptors, descriptorIndexes])
4148

4249
const deferredData = useDeferredValue(newData)
4350

src/data-list-row.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@ import {
1010
* Connects a single item row to the list.
1111
*/
1212
export function DataListRow<TRenderItem>(descriptor: DataListDescriptor<TRenderItem>) {
13-
const {attachDescriptors} = useDataListDescriptorContext()
13+
const {attachDescriptors, markForIndex} = useDataListDescriptorContext()
14+
15+
const id = React.useId()
16+
React.useLayoutEffect(() => {
17+
const detach = attachDescriptors(id, [descriptor])
18+
19+
return detach
20+
}, [attachDescriptors, descriptor])
1421

1522
const index = useDataListDescriptorDescendant({})
1623
React.useLayoutEffect(() => {
17-
const detach = attachDescriptors(index, [descriptor])
24+
const detach = markForIndex(id, index)
1825

1926
return detach
20-
}, [attachDescriptors, descriptor, index])
27+
}, [markForIndex, index])
2128

2229
return null
2330
}

src/data-list-rows.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@ interface DataListRowsProps<TRenderItem> {
1414
* Connects a single item row to the list.
1515
*/
1616
export function DataListRows<TRenderItem>({descriptors}: DataListRowsProps<TRenderItem>) {
17-
const {attachDescriptors} = useDataListDescriptorContext()
17+
const {attachDescriptors, markForIndex} = useDataListDescriptorContext()
18+
19+
const id = React.useId()
20+
React.useLayoutEffect(() => {
21+
const detach = attachDescriptors(id, descriptors)
22+
23+
return detach
24+
}, [attachDescriptors, descriptors])
1825

1926
const index = useDataListDescriptorDescendant({})
2027
React.useLayoutEffect(() => {
21-
const detach = attachDescriptors(index, descriptors)
28+
const detach = markForIndex(id, index)
2229

2330
return detach
24-
}, [attachDescriptors, descriptors, index])
31+
}, [markForIndex, index])
2532

2633
return null
2734
}

src/data-list-types.d.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,21 @@ export type DataListRenderListItem<TRenderItem> = (
7474
props: DataListRenderListItemInfoWithIndex<TRenderItem>
7575
) => React.ReactElement | null
7676

77+
type DescriptorId = string
78+
7779
/**
7880
* The internal array of descriptors.
7981
*/
80-
export type DataListDescriptors<TRenderItem = any> = Map<
81-
number,
82+
export type DataListAllDescriptors<TRenderItem = any> = Map<
83+
DescriptorId,
8284
Array<DataListDescriptor<TRenderItem>>
8385
>
8486

87+
/**
88+
* Tracks the position of descriptors at particular indexes.
89+
*/
90+
export type DataListDescriptorIndexes = Map<number, DescriptorId>
91+
8592
/**
8693
* The internal data array.
8794
* This is similar to DataListDescriptors, but has been flattened according

src/data-list.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import React from "react"
22

3-
import type {DataListDescriptors as DataListDescriptorsType} from "./data-list-types"
3+
import type {DataListAllDescriptors, DataListDescriptorIndexes} from "./data-list-types"
44
import {DataListDescriptors, type DataListDescriptorsProps} from "./data-list-descriptors"
55
import {DataListMasterRenderer, type DataListMasterRendererProps} from "./data-list-master-renderer"
66

77
export interface DataListProps<TRenderItem>
8-
extends Omit<DataListMasterRendererProps<TRenderItem>, "descriptors" | "updateDescriptorIndex">,
9-
Omit<DataListDescriptorsProps<TRenderItem>, "descriptors" | "setDescriptors"> {
8+
extends Omit<DataListMasterRendererProps<TRenderItem>, "allDescriptors" | "descriptorIndexes">,
9+
Omit<
10+
DataListDescriptorsProps<TRenderItem>,
11+
"rowChildren" | "setAllDescriptors" | "setDescriptorIndexes"
12+
> {
1013
children: React.ReactNode
1114
}
1215

@@ -17,14 +20,26 @@ export interface DataListProps<TRenderItem>
1720
* Supports familiar React concepts such as Suspense and Error boundaries.
1821
*/
1922
export function DataList<TRenderItem>({renderer, children, ...rest}: DataListProps<TRenderItem>) {
20-
const [descriptors, setDescriptors] = React.useState<DataListDescriptorsType<TRenderItem>>(
23+
const [allDescriptors, setAllDescriptors] = React.useState<DataListAllDescriptors<TRenderItem>>(
24+
new Map()
25+
)
26+
const [descriptorIndexes, setDescriptorIndexes] = React.useState<DataListDescriptorIndexes>(
2127
new Map()
2228
)
2329

2430
return (
2531
<>
26-
<DataListDescriptors setDescriptors={setDescriptors} descriptors={children} />
27-
<DataListMasterRenderer {...rest} renderer={renderer} descriptors={descriptors} />
32+
<DataListDescriptors
33+
setAllDescriptors={setAllDescriptors}
34+
setDescriptorIndexes={setDescriptorIndexes}
35+
rowChildren={children}
36+
/>
37+
<DataListMasterRenderer
38+
{...rest}
39+
renderer={renderer}
40+
allDescriptors={allDescriptors}
41+
descriptorIndexes={descriptorIndexes}
42+
/>
2843
</>
2944
)
3045
}

0 commit comments

Comments
 (0)