Skip to content

Commit b6f0e1f

Browse files
authored
fix: Initial render flickering and content overflow (#89)
## Description Fix container height collapsing and content flickering during the initial render (during transition from the `relative` to `absolute` layout).
1 parent 8c2eb6b commit b6f0e1f

File tree

19 files changed

+312
-173
lines changed

19 files changed

+312
-173
lines changed

example/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"react-native-safe-area-context": "^4.10.3",
1515
"react-native-screens": "3.32.0",
1616
"react-native-sortable": "workspace:*",
17-
"react-native-svg": "^15.3.0"
17+
"react-native-svg": "15.3.0"
1818
},
1919
"devDependencies": {
2020
"@babel/core": "^7.24.7",

example/app/src/components/cards/RouteCard.tsx

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { faChevronRight } from '@fortawesome/free-solid-svg-icons';
22
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
33
import { useNavigation } from '@react-navigation/native';
4-
import type { PropsWithChildren } from 'react';
4+
import { type PropsWithChildren, useState } from 'react';
55
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
6+
import { Defs, LinearGradient, Rect, Stop, Svg } from 'react-native-svg';
67

78
import { colors, radius, spacing, text } from '@/theme';
89

@@ -17,6 +18,7 @@ export type RouteCardComponent = (
1718

1819
export default function RouteCard({ children, route, title }: RouteCardProps) {
1920
const navigation = useNavigation();
21+
const [dimensions, setDimensions] = useState({ height: 0, width: 0 });
2022

2123
return (
2224
<TouchableOpacity
@@ -25,7 +27,48 @@ export default function RouteCard({ children, route, title }: RouteCardProps) {
2527
onPress={() => {
2628
navigation.navigate(route as never);
2729
}}>
28-
{children}
30+
{children && (
31+
<View>
32+
{children}
33+
{/* Gradient overlay */}
34+
<View
35+
style={[styles.gradient, StyleSheet.absoluteFill]}
36+
onLayout={e => {
37+
setDimensions({
38+
height: e.nativeEvent.layout.height,
39+
width: e.nativeEvent.layout.width
40+
});
41+
}}>
42+
<Svg height={dimensions.height} width={dimensions.width}>
43+
<Defs>
44+
<LinearGradient
45+
id='horizontal-gradient'
46+
x1='0'
47+
x2='1'
48+
y1='0'
49+
y2='0'>
50+
<Stop
51+
offset='0.25'
52+
stopColor={colors.background1}
53+
stopOpacity='0'
54+
/>
55+
<Stop
56+
offset='0.95'
57+
stopColor={colors.background1}
58+
stopOpacity='1'
59+
/>
60+
</LinearGradient>
61+
</Defs>
62+
63+
<Rect
64+
fill='url(#horizontal-gradient)'
65+
height='100%'
66+
width='100%'
67+
/>
68+
</Svg>
69+
</View>
70+
</View>
71+
)}
2972
<View style={styles.footer}>
3073
<Text style={[text.label1, styles.title]}>{title}</Text>
3174
<FontAwesomeIcon color={colors.foreground3} icon={faChevronRight} />
@@ -47,6 +90,10 @@ const styles = StyleSheet.create({
4790
flexDirection: 'row',
4891
justifyContent: 'space-between'
4992
},
93+
gradient: {
94+
marginBottom: -spacing.md,
95+
marginHorizontal: -spacing.lg
96+
},
5097
title: {
5198
color: colors.foreground1
5299
}

example/app/src/components/cards/SortableFlexCard.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { StyleSheet, Text } from 'react-native';
22
import { SortableFlex } from 'react-native-sortable';
33

4-
import { useItemOrderChange } from '@/hooks';
54
import { colors, radius, spacing } from '@/theme';
65

76
import type { RouteCardComponent } from './RouteCard';
@@ -23,12 +22,12 @@ const ACTIVE_INDEX = 2;
2322
const ACTIVE_ITEM = DATA[ACTIVE_INDEX];
2423

2524
const SortableFlexCard: RouteCardComponent = props => {
26-
const data = useItemOrderChange(DATA, ACTIVE_INDEX);
25+
// const data = useItemOrderChange(DATA, ACTIVE_INDEX);
2726

2827
return (
2928
<RouteCard {...props}>
3029
<SortableFlex dragEnabled={false} style={styles.container}>
31-
{data.map(item => (
30+
{DATA.map(item => (
3231
<Text
3332
key={item}
3433
style={[styles.cell, item === ACTIVE_ITEM && styles.activeCell]}>

example/app/src/components/cards/SortableGridCard.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { StyleSheet, Text, View } from 'react-native';
33
import type { SortableGridRenderItem } from 'react-native-sortable';
44
import { SortableGrid } from 'react-native-sortable';
55

6-
import { useItemOrderChange } from '@/hooks';
76
import { colors, radius, spacing } from '@/theme';
87

98
import type { RouteCardComponent } from './RouteCard';
@@ -15,7 +14,7 @@ const ACTIVE_INDEX = 6;
1514
const ACTIVE_ITEM = DATA[ACTIVE_INDEX];
1615

1716
const SortableGridCard: RouteCardComponent = props => {
18-
const data = useItemOrderChange(DATA, ACTIVE_INDEX);
17+
// const data = useItemOrderChange(DATA, ACTIVE_INDEX);
1918

2019
const renderItem = useCallback<SortableGridRenderItem<string>>(
2120
({ item }) => (
@@ -31,7 +30,7 @@ const SortableGridCard: RouteCardComponent = props => {
3130
<SortableGrid
3231
columnGap={spacing.xxs}
3332
columns={6}
34-
data={data}
33+
data={DATA}
3534
dragEnabled={false}
3635
renderItem={renderItem}
3736
rowGap={spacing.xxs}

example/fabric/ios/Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,7 @@ PODS:
13381338
- ReactCommon/turbomodule/bridging
13391339
- ReactCommon/turbomodule/core
13401340
- Yoga
1341-
- RNSVG (15.4.0):
1341+
- RNSVG (15.3.0):
13421342
- DoubleConversion
13431343
- glog
13441344
- hermes-engine
@@ -1358,9 +1358,9 @@ PODS:
13581358
- React-utils
13591359
- ReactCommon/turbomodule/bridging
13601360
- ReactCommon/turbomodule/core
1361-
- RNSVG/common (= 15.4.0)
1361+
- RNSVG/common (= 15.3.0)
13621362
- Yoga
1363-
- RNSVG/common (15.4.0):
1363+
- RNSVG/common (15.3.0):
13641364
- DoubleConversion
13651365
- glog
13661366
- hermes-engine
@@ -1634,7 +1634,7 @@ SPEC CHECKSUMS:
16341634
RNGestureHandler: 7eca02f7f0974365b00905507ed8be6277826eaf
16351635
RNReanimated: 96bff8fa77c96d2c323032650ee858a808f6edcc
16361636
RNScreens: d3d50aa84db4541eee00fbb1f32151030f56c510
1637-
RNSVG: 59c26dfc3de7a1c6a27a6e17d45c5e8957461943
1637+
RNSVG: 7b44aa4df2587946e28496adf2e10a16f08c1250
16381638
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
16391639
Yoga: 6259f968a4fdf516d76a4432dead624d71aa0fb1
16401640

example/fabric/ios/reactNativeSortableFabric.xcworkspace/contents.xcworkspacedata

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/paper/ios/Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,7 @@ PODS:
13381338
- ReactCommon/turbomodule/bridging
13391339
- ReactCommon/turbomodule/core
13401340
- Yoga
1341-
- RNSVG (15.4.0):
1341+
- RNSVG (15.3.0):
13421342
- DoubleConversion
13431343
- glog
13441344
- hermes-engine
@@ -1358,9 +1358,9 @@ PODS:
13581358
- React-utils
13591359
- ReactCommon/turbomodule/bridging
13601360
- ReactCommon/turbomodule/core
1361-
- RNSVG/common (= 15.4.0)
1361+
- RNSVG/common (= 15.3.0)
13621362
- Yoga
1363-
- RNSVG/common (15.4.0):
1363+
- RNSVG/common (15.3.0):
13641364
- DoubleConversion
13651365
- glog
13661366
- hermes-engine
@@ -1634,7 +1634,7 @@ SPEC CHECKSUMS:
16341634
RNGestureHandler: 7eca02f7f0974365b00905507ed8be6277826eaf
16351635
RNReanimated: 96bff8fa77c96d2c323032650ee858a808f6edcc
16361636
RNScreens: d3d50aa84db4541eee00fbb1f32151030f56c510
1637-
RNSVG: 59c26dfc3de7a1c6a27a6e17d45c5e8957461943
1637+
RNSVG: 7b44aa4df2587946e28496adf2e10a16f08c1250
16381638
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
16391639
Yoga: 6259f968a4fdf516d76a4432dead624d71aa0fb1
16401640

packages/react-native-sortable/src/components/SortableFlex/SortableFlex.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,32 @@ function SortableFlexInner({
5858
reorderStrategy,
5959
viewProps
6060
}: SortableFlexInnerProps) {
61-
const { containerHeight } = useMeasurementsContext();
61+
const { canSwitchToAbsoluteLayout, containerHeight } =
62+
useMeasurementsContext();
6263
const { flexDirection, stretch } = useFlexLayoutContext();
6364

6465
useFlexOrderUpdater(reorderStrategy);
6566

66-
const animatedContainerHeightStyle = useAnimatedStyle(() => ({
67-
height:
68-
containerHeight.value === -1
69-
? (viewProps?.style as ViewStyle)?.height
70-
: containerHeight.value
71-
}));
67+
const animatedContainerStyle = useAnimatedStyle(() => {
68+
if (containerHeight.value === -1) {
69+
return {};
70+
}
71+
const style: ViewStyle = { height: containerHeight.value };
72+
if (canSwitchToAbsoluteLayout) {
73+
style.justifyContent = 'flex-start';
74+
style.alignItems = 'flex-start';
75+
}
76+
return style;
77+
});
7278

7379
return (
7480
<Animated.View
7581
{...viewProps}
76-
style={[viewProps.style, animatedContainerHeightStyle]}>
82+
style={[
83+
viewProps.style,
84+
animatedContainerStyle,
85+
{ backgroundColor: 'green' }
86+
]}>
7787
{childrenArray.map(([key, child]) => (
7888
<DraggableView
7989
itemKey={key}

packages/react-native-sortable/src/components/SortableGrid/SortableGrid.tsx

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { useMemo } from 'react';
22
import type { ViewProps, ViewStyle } from 'react-native';
3-
import { StyleSheet } from 'react-native';
4-
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
3+
import { StyleSheet, View } from 'react-native';
54

65
import {
76
GridLayoutProvider,
87
SharedProvider,
9-
useGridOrderUpdater,
10-
useMeasurementsContext
8+
useGridOrderUpdater
119
} from '../../contexts';
1210
import type { Prettify, SharedProps } from '../../types';
1311
import {
@@ -53,22 +51,26 @@ function SortableGrid<I>(props: SortableGridProps<I>) {
5351
marginVertical: -rowGap / 2
5452
};
5553

54+
const sharedProps = {
55+
columnGap,
56+
columns,
57+
rowGap
58+
};
59+
5660
return (
5761
<SharedProvider
5862
{...providerProps}
5963
dropIndicatorStyle={containerSpacingStyle}
6064
itemKeys={itemKeys}
6165
key={columns}>
62-
<GridLayoutProvider columnCount={columns} columnGap={columnGap}>
66+
<GridLayoutProvider {...sharedProps}>
6367
<SortableGridInner
64-
columnGap={columnGap}
65-
columns={columns}
6668
containerSpacingStyle={containerSpacingStyle}
6769
data={data}
6870
itemKeys={itemKeys}
6971
renderItem={renderItem}
7072
reorderStrategy={reorderStrategy}
71-
rowGap={rowGap}
73+
{...sharedProps}
7274
/>
7375
</GridLayoutProvider>
7476
</SharedProvider>
@@ -100,7 +102,6 @@ function SortableGridInner<I>({
100102
reorderStrategy,
101103
rowGap
102104
}: SortableGridInnerProps<I>) {
103-
const { containerHeight } = useMeasurementsContext();
104105
useGridOrderUpdater(columns, reorderStrategy);
105106

106107
const columnWidthStyle = useMemo<ViewStyle>(
@@ -110,17 +111,8 @@ function SortableGridInner<I>({
110111
[columns]
111112
);
112113

113-
const animatedContainerHeightStyle = useAnimatedStyle(() => ({
114-
height: containerHeight.value === -1 ? 'auto' : containerHeight.value
115-
}));
116-
117114
return (
118-
<Animated.View
119-
style={[
120-
styles.gridContainer,
121-
containerSpacingStyle,
122-
animatedContainerHeightStyle
123-
]}>
115+
<View style={[styles.gridContainer, containerSpacingStyle]}>
124116
{zipArrays(data, itemKeys).map(([item, key]) => (
125117
<SortableGridItem
126118
columnGap={columnGap}
@@ -132,7 +124,7 @@ function SortableGridInner<I>({
132124
style={columnWidthStyle}
133125
/>
134126
))}
135-
</Animated.View>
127+
</View>
136128
);
137129
}
138130

0 commit comments

Comments
 (0)