Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save eveningkid/bda4c848b90416e930a5621ac392dee6 to your computer and use it in GitHub Desktop.
Save eveningkid/bda4c848b90416e930a5621ac392dee6 to your computer and use it in GitHub Desktop.
React Native Shared Elements: Facebook Marketplace Example
// Expo SDK40
// @react-native-community/masked-view: 0.1.10
// @react-navigation/native: ^5.9.3
// @react-navigation/stack: ^5.14.3
// react-native-gesture-handler: ~1.8.0
// react-native-reanimated: ~1.13.0
// react-native-safe-area-context: 3.1.9
// react-native-screens: ~2.15.2
// react-native-shared-element: 0.7.0
// react-navigation: 4
// react-navigation-shared-element: ^3.0.0
import React, { useEffect, useRef } from 'react';
import {
Animated,
Image,
Pressable,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
useWindowDimensions,
View,
} from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
import {
SharedElement,
createSharedElementStackNavigator,
} from 'react-navigation-shared-element';
import POSTS from './posts';
import Button from './Button';
import Icon from './Icon';
const SPACING = 15;
const POST_GUTTER_WIDTH = 2;
const ListScreen = ({ navigation }) => {
const dimensions = useWindowDimensions();
const imageWidth = dimensions.width / 2 - POST_GUTTER_WIDTH;
return (
<SafeAreaView style={styles.wrapper}>
<ScrollView style={styles.wrapper}>
<Text style={styles.listHeader}>Marketplace</Text>
<View style={styles.posts}>
{POSTS.map((post, index) => (
<Pressable
key={post.id}
onPress={() =>
navigation.push('Detail', {
post,
})
}
style={{
width: imageWidth,
}}
>
<SharedElement id={post.id}>
<Image
source={post.image}
style={{
height: 180,
width: imageWidth,
marginRight:
index % 2 === 1 ? 0 : POST_GUTTER_WIDTH,
marginLeft:
index % 2 === 1 ? POST_GUTTER_WIDTH : 0,
}}
/>
</SharedElement>
<View style={styles.postTexts}>
<Text numberOfLines={1} style={styles.postHeader}>
€{post.price} · {post.title}
</Text>
<Text
numberOfLines={1}
style={styles.postDescription}
>
{post.description}
</Text>
</View>
</Pressable>
))}
</View>
</ScrollView>
</SafeAreaView>
);
};
const DetailScreen = ({ route, navigation }) => {
const { post } = route.params;
const safeInsets = useSafeAreaInsets();
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 250,
delay: 500,
useNativeDriver: true,
}).start();
}, []);
return (
<View style={styles.wrapper}>
<Animated.View
style={{
opacity,
position: 'absolute',
top: safeInsets.top + SPACING,
left: safeInsets.left + SPACING,
right: safeInsets.right + SPACING,
zIndex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
<Icon name="x" onPress={() => navigation.goBack()} />
<Icon name="more-horizontal" />
</Animated.View>
<SharedElement id={post.id}>
<Image source={post.image} style={styles.postImage} />
</SharedElement>
<View style={styles.postDetails}>
<Text style={styles.postTitle}>{post.title}</Text>
<Text style={styles.postPrice}>€{post.price}</Text>
<Button
title="Contact Seller"
style={styles.postContactButton}
/>
<Animated.Text
style={{
opacity,
fontSize: 17,
}}
>
{post.description}
</Animated.Text>
</View>
</View>
);
};
const Stack = createSharedElementStackNavigator();
const MainScreen = () => (
<Stack.Navigator
mode="modal"
screenOptions={{ headerShown: false }}
>
<Stack.Screen name="List" component={ListScreen} />
<Stack.Screen
name="Detail"
component={DetailScreen}
sharedElements={(route) => {
return [route.params.post.id];
}}
/>
</Stack.Navigator>
);
export default function App() {
return (
<SafeAreaProvider>
<NavigationContainer>
<MainScreen />
</NavigationContainer>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
},
listHeader: {
fontSize: 28,
fontWeight: '800',
margin: SPACING,
},
posts: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
},
postTexts: {
margin: 10,
marginBottom: 15,
},
postHeader: {
fontWeight: '600',
},
postDescription: {
color: 'gray',
},
postImage: {
height: 300,
width: '100%',
},
postDetails: {
paddingVertical: 10,
paddingHorizontal: SPACING,
},
postTitle: {
fontSize: 24,
fontWeight: 'bold',
},
postPrice: {
fontSize: 24,
},
postContactButton: {
marginVertical: SPACING,
},
});
@aframson
Copy link

aframson commented Apr 3, 2021

this is so cool

@geovrisco
Copy link

is it possible to wrap Animated.View in shared components?

@eveningkid
Copy link
Author

@geovrisco I don't see why it wouldn't be possible.

If you ever try, please update us here so this could help others wondering the same thing :)

@longtq-younet
Copy link

longtq-younet commented Aug 8, 2021

where are the files you imported ? posts? Button? Icon?

@eveningkid
Copy link
Author

@longtq-younet From what I remember, you can see how it looks like in the following video. It is just boilerplate code!

@matifriaz
Copy link

I'm using it with @react-navigation v6 and getting errors

@bennkingy
Copy link

Litteraly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment