Skip to content

Commit e403aa6

Browse files
iamdavidmartinregalstreak
authored andcommitted
fix: shared element flicker of same named screen transitions
(check example) In createSharedElementScene.tsx, changed function to use route.key instead of route.name when checking to see if the current route is active. Previously, all screens that had the same name inthe stack think that they are active when the navigation event fires. This correctly finds the one screen that is active to perform the correct transition. In SharedElementRendererData, when updating share elements, use variable to correctly send the screen's 'sharedElements' function the showing state. This fixes a bug where, if a user went backwards to a screen of the same name, would be true when it should be false. Please take a look at the example I added. These fixes now allow for properly navigating back and forthwith a screen of the same name.
1 parent e432c6c commit e403aa6

File tree

7 files changed

+213
-3
lines changed

7 files changed

+213
-3
lines changed

example/src/App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import NestedStack from "./tests/NestedStack";
3333
import NestedStackV4 from "./tests/NestedStack.v4";
3434
import NestedStack2 from "./tests/NestedStack2";
3535
import NestedStack2V4 from "./tests/NestedStack2.v4";
36+
import PushPopSameScreen2 from "./tests/PushPopSameScreen2";
3637
import PushPopSameScreen from "./tests/PushPopSameScreen";
3738
import PushPopSameScreenV4 from "./tests/PushPopSameScreen.v4";
3839
import RevealFromBottomAndroid from "./tests/RevealFromBottomAndroid";
@@ -88,6 +89,12 @@ export default () => (
8889
Component={ForwardOnly}
8990
/>
9091
<Test title="BackOnly" ComponentV4={BackOnlyV4} Component={BackOnly} />
92+
<Test
93+
title="PushPopSameScreen - v2"
94+
ComponentV4={PushPopSameScreen2}
95+
Component={PushPopSameScreen2}
96+
issue={["v4"]}
97+
/>
9198
<Test
9299
title="PushPopSameScreen"
93100
ComponentV4={PushPopSameScreen}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import * as React from "react";
2+
import { StyleSheet, View, Image, Text } from "react-native";
3+
import { NavigationStackProp } from "react-navigation-stack";
4+
5+
import { TouchableScale } from "../components";
6+
import { Item, defaultItem, items } from "../data";
7+
import { DetailComponent } from "./DetailComponent";
8+
import { getMoreDetailSharedElements } from "./getMoreDetailsSharedElements";
9+
import { SharedElement } from "react-navigation-shared-element";
10+
11+
type Props = {
12+
navigation: NavigationStackProp<any>;
13+
route: any; // v5
14+
modal: "none" | "full" | "sheet";
15+
onPress?: ({
16+
navigation,
17+
item,
18+
nextItem,
19+
}: {
20+
navigation: NavigationStackProp<any>;
21+
item?: Item;
22+
nextItem?: Item;
23+
}) => void;
24+
};
25+
26+
export const DetailScreenWithMore = (props: Props) => {
27+
const { navigation, route, modal, onPress } = props;
28+
const params = route?.params || navigation?.state?.params;
29+
const item: Item = params?.item || defaultItem;
30+
const content = (
31+
<DetailComponent item={item} navigation={navigation} modal={modal} />
32+
);
33+
34+
// get 3 items that aren't the item we're viewing now
35+
const moreItems = items.filter((_i) => _i.id !== item.id).slice(0, 3);
36+
const moreContent = (
37+
<View
38+
style={{
39+
zIndex: 1000,
40+
bottom: 20,
41+
left: 0,
42+
right: 0,
43+
position: "absolute",
44+
flexDirection: "row",
45+
justifyContent: "space-between",
46+
paddingHorizontal: 20,
47+
}}
48+
>
49+
{moreItems.map((_i, index) => (
50+
<TouchableScale
51+
key={index}
52+
style={styles.flex}
53+
onPress={
54+
onPress ? () => onPress({ navigation, nextItem: _i }) : undefined
55+
}
56+
>
57+
<View style={styles.container}>
58+
<SharedElement id={`${_i.id}.image`}>
59+
<Image style={styles.image} source={_i.image} />
60+
</SharedElement>
61+
<SharedElement id={`${_i.id}.title`}>
62+
<Text style={styles.text}>{`${_i.title}`}</Text>
63+
</SharedElement>
64+
<Text style={styles.caption}>tap me</Text>
65+
</View>
66+
</TouchableScale>
67+
))}
68+
</View>
69+
);
70+
return (
71+
<>
72+
{moreContent}
73+
{content}
74+
</>
75+
);
76+
};
77+
78+
DetailScreenWithMore.navigationOptions = {
79+
title: "Boys will be boys",
80+
};
81+
82+
// Add the `sharedElements` function to the component, which
83+
// should return a list of shared-elements to transition.
84+
// The `sharedElements` function is called whenever you navigate
85+
// to or from this screen. You can use the provided navigation
86+
// states or trigger or disable animations.
87+
DetailScreenWithMore.sharedElements = getMoreDetailSharedElements;
88+
89+
const styles = StyleSheet.create({
90+
flex: {
91+
width: 100,
92+
backgroundColor: "white",
93+
borderRadius: 50,
94+
padding: 10,
95+
},
96+
container: {
97+
flex: 1,
98+
justifyContent: "center",
99+
alignItems: "center",
100+
},
101+
text: {
102+
fontSize: 15,
103+
},
104+
caption: {
105+
fontSize: 15,
106+
opacity: 0.5,
107+
},
108+
image: {
109+
width: 50,
110+
height: 30,
111+
resizeMode: "cover",
112+
},
113+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { SharedElementsComponentConfig } from "react-navigation-shared-element";
2+
3+
export const getMoreDetailSharedElements: SharedElementsComponentConfig = (
4+
route,
5+
otherRoute,
6+
showing
7+
) => {
8+
let item = undefined;
9+
if (!showing && route.name == otherRoute.name) {
10+
item = otherRoute.params.item;
11+
} else {
12+
item = route.params.item;
13+
}
14+
return [
15+
{ id: `${item.id}.image` },
16+
{ id: `${item.id}.title`, animation: "fade" },
17+
{ id: "close", animation: "fade" },
18+
];
19+
};

example/src/screens/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "./MainScreen";
22
export * from "./DetailScreen";
3+
export * from "./DetailScreenWithMore";
34
export * from "./DetailScreenImageBackground";
45
export * from "./DetailPagerScreen";
56
export * from "./ListScreen";
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { NavigationContainer } from "@react-navigation/native";
2+
import * as React from "react";
3+
import { createSharedElementStackNavigator } from "react-navigation-shared-element";
4+
5+
import { Item, items } from "../data";
6+
import { createScreen, DetailScreenWithMore } from "../screens";
7+
8+
const name = "PushPopSameScreen2";
9+
10+
const Stack = createSharedElementStackNavigator({
11+
name,
12+
debug: false,
13+
});
14+
15+
const PressableDetailWithMoreScreen = createScreen(
16+
DetailScreenWithMore,
17+
undefined,
18+
undefined,
19+
{
20+
onPress: (event: any) => {
21+
let nextItem = event.nextItem;
22+
if (event.item) {
23+
const item: Item = event.item;
24+
const itemIdx = items.indexOf(item);
25+
nextItem = items[itemIdx < items.length - 2 ? itemIdx + 1 : 0];
26+
} else {
27+
}
28+
event.navigation.push("Detail", {
29+
item: nextItem,
30+
});
31+
},
32+
}
33+
);
34+
35+
export default () => (
36+
<NavigationContainer>
37+
<Stack.Navigator>
38+
<Stack.Screen
39+
name="Detail"
40+
component={PressableDetailWithMoreScreen}
41+
initialParams={{ item: items[0] }}
42+
sharedElements={(route, otherRoute, showing) => {
43+
let item = undefined;
44+
45+
if (!showing && route.name == otherRoute.name) {
46+
// we are naviating backwards
47+
// to the same screen, so pick the params from the
48+
// otherRoute (aka "from route")
49+
item = otherRoute.params.item;
50+
} else {
51+
item = route.params.item;
52+
}
53+
return [
54+
{ id: `${item.id}.image` },
55+
{ id: `${item.id}.title`, animation: "fade" },
56+
{ id: "close", animation: "fade" },
57+
];
58+
}}
59+
/>
60+
</Stack.Navigator>
61+
</NavigationContainer>
62+
);

src/SharedElementRendererData.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,18 @@ export default class SharedElementRendererData
316316
let sharedElements: SharedElementsStrictConfig | null = null;
317317
let isShowing = true;
318318
if (sceneAnimValue && scene && prevScene && route && prevRoute) {
319-
sharedElements = getSharedElements(scene, prevScene, true);
319+
sharedElements = getSharedElements(
320+
scene,
321+
prevScene,
322+
!this.isTransitionClosing
323+
);
320324
if (!sharedElements) {
321325
isShowing = false;
322-
sharedElements = getSharedElements(prevScene, scene, false);
326+
sharedElements = getSharedElements(
327+
prevScene,
328+
scene,
329+
!this.isTransitionClosing
330+
);
323331
}
324332
}
325333
if (this.sharedElements !== sharedElements) {

src/createSharedElementScene.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function isActiveRoute(
5656
: // @ts-ignore: dangerouslyGetState is provided for navigation 5 compatibility
5757
navigation.dangerouslyGetState();
5858
const activeRoute = getActiveRoute(state);
59-
return route.name === activeRoute.name;
59+
return route.key === activeRoute.key;
6060
}
6161

6262
function createSharedElementScene(

0 commit comments

Comments
 (0)