Skip to content

Commit 529236a

Browse files
fix(useListTransition): updating an array of objects often leads to a duplicate div in the DOM (#2)
close #2
1 parent 2e73e79 commit 529236a

File tree

2 files changed

+84
-19
lines changed

2 files changed

+84
-19
lines changed

playground/src/App.tsx

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,57 @@
11
import { useState } from 'react'
2-
import { SwitchTransition } from 'transition-hooks'
2+
import { useListTransition } from 'transition-hooks'
3+
// import { startViewTransition } from 'transition-hooks/viewTransition'
34

45
export function App() {
5-
const [show, setShow] = useState(false)
6+
const [list, setList] = useState([
7+
{ id: 1, text: '1' },
8+
{ id: 2, text: '2' },
9+
])
10+
11+
const { transitionList } = useListTransition(list, {
12+
timeout: 1000,
13+
keyExtractor: i => i.id,
14+
})
15+
16+
const updateLastItem = () => {
17+
const newList = [...list]
18+
newList[newList.length - 2] = {
19+
id: newList[newList.length - 2].id,
20+
text: `${newList[newList.length - 2].text}test`,
21+
}
22+
setList(newList)
23+
}
24+
25+
const addItem = () => {
26+
setList(l => [...l, { id: l.length + 1, text: (l.length + 1).toString() }])
27+
}
628

729
return (
830
<div>
9-
<SwitchTransition state={show}>
10-
{(state, { status }) => {
31+
<div style={{ display: 'flex', gap: 4, marginBottom: 8 }}>
32+
<button onClick={addItem}>add</button>
33+
<button onClick={updateLastItem}>update last item</button>
34+
</div>
35+
<ul>
36+
{transitionList((item, { key, simpleStatus }) => {
1137
return (
12-
<div
38+
<li
1339
style={{
14-
transition: 'opacity 0.3s',
15-
opacity: status === 'entering' || status === 'entered' ? 1 : 0,
40+
position: simpleStatus === 'exit' ? 'absolute' : 'relative',
41+
opacity: simpleStatus === 'enter' ? 1 : 0,
42+
transform:
43+
simpleStatus === 'enter'
44+
? 'translateX(0)'
45+
: 'translateX(20px)',
46+
transition: 'all .6s',
47+
// viewTransitionName: simpleStatus === 'enter' ? `transition-list-${key}` : '',
1648
}}
1749
>
18-
Hello Word {state ? 'true' : 'false'}
19-
</div>
50+
- {item.text}
51+
</li>
2052
)
21-
}}
22-
</SwitchTransition>
23-
<button onClick={() => setShow(!show)}>toggle</button>
53+
})}
54+
</ul>
2455
</div>
2556
)
2657
}

src/hooks/useListTransition.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,43 @@ export function useListTransition<Item>(list: Array<Item>, options?: ListTransit
5050

5151
useEffect(
5252
() => {
53+
// const hasChanged = listState.length !== list.length
54+
// || (hasCustomKeyExtractor && list.some((item, index) => keyExtractor(item) !== listState[index].key))
55+
// console.log('🚀 ~ hasChanged:', hasChanged)
56+
// if (!hasChanged)
57+
// return
58+
5359
const newItemsWithIndex: Array<ItemWithKey<Item>> = []
60+
const updatedItemsWithIndex: Array<ItemWithKey<Item>> = []
5461

5562
list.forEach((item, index) => {
56-
if (listState.every(itemState => itemState.item !== item))
57-
newItemsWithIndex.push({ item, index })
63+
if (listState.every(itemState => itemState.item !== item)) {
64+
if (hasCustomKeyExtractor && listState.some(itemState => keyExtractor(item) === itemState.key))
65+
updatedItemsWithIndex.push({ item, index })
66+
else
67+
newItemsWithIndex.push({ item, index })
68+
}
5869
})
5970

71+
if (updatedItemsWithIndex.length > 0) {
72+
viewTransition(() => {
73+
setListState(prevListState => {
74+
const newListState = [...prevListState]
75+
updatedItemsWithIndex.forEach(({ item }) => {
76+
const targetIndex = newListState.findIndex(itemState => itemState.key === keyExtractor(item))
77+
if (targetIndex > -1) {
78+
const originItem = newListState[targetIndex]
79+
newListState[targetIndex] = {
80+
...originItem,
81+
item,
82+
}
83+
}
84+
})
85+
return newListState
86+
})
87+
})
88+
}
89+
6090
// 1 add new items into list state
6191
if (newItemsWithIndex.length > 0) {
6292
viewTransition(() => {
@@ -138,11 +168,15 @@ export function useListTransition<Item>(list: Array<Item>, options?: ListTransit
138168
) {
139169
viewTransition(() => {
140170
setListState(
141-
list.map(item => ({
142-
item,
143-
key: keyExtractor(item),
144-
...getState(STATUS.entered),
145-
})),
171+
list.map(item => {
172+
const key = keyExtractor(item)
173+
const originItem = listState.find(itemState => itemState.key === key)
174+
return {
175+
...(originItem || getState(STATUS.entered)),
176+
item,
177+
key,
178+
}
179+
}),
146180
)
147181
})
148182
}

0 commit comments

Comments
 (0)