What are the best practices for rendering an image based newsfeed in react native?
I've got a very simple newsfeed where each story has a cover image and then a star rating (composed of 1-5 images). On initial render, the images appear to be loaded in random order and it looks awful and non-native.
Here's a 7s video showing the screen transition and render.
Is there any way to control the order in which the images render, or not to render a story until the whole block is loaded?
If it helps, the code is below. Using IncrementalListView to render the rows. Release build, iOS 9.3, iPhone 6. Each cover image is ~55kb JPG, and the star is ~3kb PNG. Both images are packaged into the bundle.
UPDATE 3/31
I changed the code to use IncrementalListView instead of rendering directly into ScrollView, but this hasn't helped. The problem seems to be with how images are decoded and rendered, not with how rows are rendered.
class DiscoverRow extends React.Component {
render() {
let images = {
wide: {
data: require('../img/img-wide.jpg'),
height: 200,
width: 376
}
};
let title = this.props.event.name;
let date = "Tomorrow";
let place = this.props.event.venue.name;
const newHeight = images.wide.height / images.wide.width * screenWidth;
return (
<View style={[rowStyles.cell]}>
<View style={{ borderRadius: 15 }}>
<Image resizeMode={'stretch'} source={images.wide.data} style={[rowStyles.thumbnail]} />
<View style={[rowStyles.annotationsContainer]}>
<View style={rowStyles.textContainer}>
<AHStarRating starColor={gConstants.themeColor} disabled rating={4.5} style={{ width: 100 }} />
<AHText style={rowStyles.title}>{title}</AHText>
<AHText style={rowStyles.date}>{date}</AHText>
</View>
<View style={rowStyles.commentsContainer}>
<Image source={require('../img/chat.png')}
style={{ width: 36, height: 36,
tintColor: gConstants.themeColor,
backgroundColor: 'transparent' }}
/>
<TouchableWithoutFeedback onPress={this._poop}>
<Image
source={require('../img/heart.png')}
style={{ width: 36, height: 36,
tintColor: gConstants.themeColor,
backgroundColor: 'transparent' }}
/>
</TouchableWithoutFeedback>
</View>
</View>
</View>
</View>
);
}
}
class DiscoverPage extends React.Component {
static relay = {
queries: { viewer: () => Relay.QL`query { viewer }` },
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
events {
id
name
venue {
name
}
}
}
`,
},
};
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.setState({ renderPlaceholderOnly: false });
});
}
componentWillReceiveProps(nextProps) {
if (!nextProps.relayLoading) {
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.setState({
dataSource: ds.cloneWithRows(nextProps.viewer.events),
});
}
}
_renderRow(event: Object, sectionID: number, rowID: number) {
return <DiscoverRow
onPress={() => Actions.event({ event })}
key={`comment-${rowID}`} event={event}
/>;
}
render() {
if (this.props.relayLoading || this.state.renderPlaceholderOnly) {
return <View><AHText>Relay loading</AHText></View>;
} else {
return (
<View style={styles.container}>
<AHNavBar title={'Discover'} leftTitle={""} rightImage={require('../img/filter.png')} />
<IncrementalListView
initialListSize={3}
dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderSeparator={(sectionID, rowID) => <View key={`${sectionID}-${rowID}`}
style={styles.separator} />}
/>
</View>
);
}
}
}
You should implement with a ListView instead of a ScrollView. ListView has performance optimizations that improve scrolling performance.
From the ListView section in the React Native Docs:
There are a few performance operations designed to make ListView
scroll smoothly while dynamically loading potentially very large (or
conceptually infinite) data sets
Related
In my React Native applikation I render a <FlatList> with Images. I pass the direct imageurl as source into the <Image> Component.
<FlatList
data={this.state.images}
keyExtractor={item => item.imageToken}
renderItem={({ item }) => (
<Image key={item.imageToken} style={{ marginRight: 2, marginTop: 2, width: '50%', opacity: 1 }} source={{ uri: item.imageUrl }} alt="Alternate Text" size="xl" /> )} />
This means that the images are loaded in a different order because they are also different sizes. I would like to show a placeholder during loading.
The listAll() function resets isLoading to false before all images are displayed. Is there a 'trigger' when an image is fully visible in the view? I can't just build a single state for each image - I guess.
There will be many hundreds of pictures!
I think it's important to know that I extract the url from the google firestore images and store they as an array in state beforehand. See function getDownloadURL
Fullcode
import React, { Component } from 'react'
import { StyleSheet, SafeAreaView, ActivityIndicator } from 'react-native'
import { Image, FlatList, Center, Box } from "native-base"
import EventGalleryHeader from '../components/EventGalleryHeader.js'
import { getStorage, ref, getDownloadURL, list, listAll } from "firebase/storage"
import { LongPressGestureHandler, State } from 'react-native-gesture-handler'
export default class EventScreen extends Component {
constructor(props) {
super(props);
this.storage = getStorage()
this.pathToImages = '/eventimages/'
this.eventImageSource = this.props.route.params.eventData.key
this.imagesRef = this.pathToImages + this.eventImageSource
this.state = {
isLoading: true,
images: [],
event: {
adress: this.props.route.params.eventData.adress,
hosts: this.props.route.params.eventData.hosts,
description: this.props.route.params.eventData.description,
eventtitle: this.props.route.params.eventData.eventtitle,
invitecode: this.props.route.params.eventData.invitecode,
key: this.props.route.params.eventData.key,
timestamp: this.props.route.params.eventData.timestamp,
}
}
}
componentDidMount() {
this.getEventImageData()
}
componentWillUnmount() {
}
getEventImageData() {
const images = []
const event = {
adress: this.props.route.params.eventData.adress,
description: this.props.route.params.eventData.description,
eventtitle: this.props.route.params.eventData.eventtitle,
key: this.props.route.params.eventData.key,
timestamp: this.props.route.params.eventData.timestamp,
}
listAll(ref(this.storage, this.imagesRef))
.then((res) => {
res.items.forEach((itemRef) => {
getDownloadURL(itemRef)
.then((url) => {
const indexOfToken = url.indexOf("&token=")
const token = url.slice(indexOfToken + 7)
images.push({
"imageUrl": url,
"imageToken": token
});
this.setState({
images,
event,
isLoading: false,
});
// console.log(this.state.images)
})
.catch((error) => {
switch (error.code) {
case 'storage/object-not-found':
break;
case 'storage/unauthorized':
break;
case 'storage/canceled':
break;
case 'storage/unknown':
break;
}
});
});
}).catch((error) => {
});
}
onLongPress = (event) => {
if (event.nativeEvent.state === State.ACTIVE) {
alert("I've been pressed for 800 milliseconds");
}
};
render() {
if (this.state.isLoading) {
return (<Center style={styles.container} _dark={{ bg: "blueGray.900" }} _light={{ bg: "blueGray.50" }}>
<ActivityIndicator size="large" color="#22d3ee" />
</Center>
)
} else {
return (
<SafeAreaView style={styles.container} >
<FlatList _dark={{ bg: "blueGray.900" }} _light={{ bg: "blueGray.50" }}
style={styles.list}
numColumns={2}
ListHeaderComponent={<EventGalleryHeader data={this.state.event} />}
data={this.state.images}
keyExtractor={item => item.imageToken}
renderItem={({ item }) => (
<LongPressGestureHandler
onHandlerStateChange={this.onLongPress}
minDurationMs={800}
>
<Image key={item.imageToken} style={{ marginRight: 2, marginTop: 2, width: '50%', opacity: 1 }} source={{ uri: item.imageUrl }} alt="Alternate Text" size="xl" />
</LongPressGestureHandler>
)}
/>
</SafeAreaView>
);
}
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
image: {
maxHeight: 450,
width: '100%',
height: 200,
overflow: 'hidden',
},
list: {
alignSelf: 'center',
},
gallery: {
flex: 1,
width: '100%',
flexDirection: 'row',
}
})
And again it shows how important it is to read the documentation properly beforehand and to look there first if you have any questions.
You can achieve the behavior I mentioned above with the following parameters.
loadingIndicatorSource link
Similarly to source, this property represents the resource used to render the loading indicator for the image, displayed until image is ready to be displayed, typically after when it got downloaded from network.
onLoad link
Invoked when load completes successfully.
onLoadEnd link
Invoked when load either succeeds or fails.
onLoadStart link
Invoked on load start.
Example: onLoadStart={() => this.setState({loading: true})}
I hope you're doing okay
I'm experiencing something weird with my react-native project, The FlatList items in some of the pages aren't displayed, even though I can see them when I console.log(json.items).
Earlier today, everything worked fine. all the pages displayed their lists as they should on my device. then I started working on a new search page on snack & I added status bar, I tested and it worked on snack before adding the new code and creating the new files in my app.
The issue I'm having now is, the list on the first page is displayed, subsequent pages after that do not show list items. including the new search page that works on snack
I'll go ahead and post my code now, the first set is for the page whose listitems are displayed correctly:
App.js
class ProfileActivity extends Component
{
// Setting up profile activity title.
static navigationOptions = ({ navigation }) =>
{
return {
title: 'Home',
headerStyle : {
backgroundColor: '#00b47a',
elevation: 0
},
headerTitleStyle: {
color: 'white'
},
cardStyle: { backgroundColor: '#fff' },
headerLeft: null,
headerRight: (
<View style={{ alignSelf: 'center', alignItems: 'center', display: 'flex', flexDirection: 'row', justifyContent: 'space-evenly'}}>
<Icon containerStyle={{ paddingRight: 10 }}
color='#fff' onPress={()=> navigation.getParam('openBottomSheet')()}
name="menu" />
<Icon containerStyle={{ paddingRight: 15 }}
color='#fff' onPress={()=> navigation.getParam('openSearchPage')()}
name="search" /></View>
)
}
};
constructor () {
super()
this.state = { toggled: false }
}
componentDidMount() {
this.props.navigation.setParams({ openBottomSheet: this.onOpenBottomSheet });
this.props.navigation.setParams({ openSearchPage: this.onOpenSearchPage });
}
onOpenBottomSheet = () => {
this.Standard.open();
}
onOpenSearchPage = () => {
this.props.navigation.navigate('sixth');
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<StatusBar
animated={true}
backgroundColor="#00b47a"
barStyle={'light-content'}
showHideTransition={'slide'}
hidden={false} />
<MarketList navigation={this.props.navigation} />
<RBSheet
ref={ref => {
this.Standard = ref;
}}
>
...
</RBSheet>
</View>
);
}
}
Market.js
export default class MarketList extends React.Component {
constructor(props) {
super(props)
this.state = {
items: '',
};
this.animateList = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(this.animateList, {
toValue: 1,
duration: 500,
}).start();
}
render() {
const rowStyles = [
styles.itemList,
{ opacity: this.animateList },
{
transform: [
{ scale: this.animateList },
],
},
];
fetch('https://mvmarket.xyz/nativeuserapp/home.php')
.then((response) => response.json())
.then((json) => {
this.setState({
items: json.items,
})
})
.catch((error) => {
console.error(error);
});
return (
<View style={styles.container}>
<FlatList
data={this.state.items}
renderItem={({item}) => <TouchableOpacity onPress={()=>this.props.navigation.navigate("fourth",{market:item.name})}><Animated.View style={rowStyles}><View style={styles.item}><Text style={styles.market}>{item.name}</Text><Text style={styles.location}>{item.Location}</Text></View><View style={styles.go}><Icon name="arrow-right" color="#00b47a" /></View></Animated.View></TouchableOpacity>}
/>
</View>
);
}
}
This next set is for one of the pages that don't show list items
App.js
class ProductsActivity extends Component {
static navigationOptions =
{
title: 'Products',
headerStyle : {
backgroundColor: '#00b47a',
elevation: 0
},
cardStyle: { backgroundColor: '#fff' },
headerTitleStyle: {
color: 'white'
},
};
render() {
return(
<View>
<StatusBar
animated={true}
backgroundColor="#00b47a"
barStyle={'light-content'}
showHideTransition={'slide'}
hidden={false} />
<ProductsList navigation={this.props.navigation} />
</View>
);
}
}
Products.js
export default class ProductsList extends React.Component {
constructor(props) {
super(props)
this.state = {
items: '',
};
}
render() {
fetch('https://mvmarket.xyz/nativeuserapp/products.php')
.then((response) => response.json())
.then((json) => {
this.setState({
items: json.items,
})
}).catch((error) => {
console.error(error);
console.log(error);
});
return (
<View style={styles.container}>
<FlatList
data={this.state.items}
renderItem={({item}) => <TouchableOpacity onPress={()=>this.props.navigation.navigate("fifth",{market: this.props.navigation.state.params.market, type:item.type})} style={styles.itemList}><View style={styles.item}><Text style={styles.market}>{item.type}</Text></View><View style={styles.go}><Icon name="arrow-right" color="#00b47a" /></View></TouchableOpacity>}
/>
</View>
);
}
}
I'm leaving the URL there so you can confirm yourself that the data is actually fetched. Its driving me crazy, been on it for like 4 hrs.
Thank you
I think you don't really understand lifecycle methods of a React Component. It's important to understand those concepts before jumping into code. You can check here
When you put your fetch call in render, and on then you do a setState() you are making this infinitely. This happens because you are always providing new values to items.
The ideal is to have a model layer to handle those type of calls, but this is an architecture thing, to be less complex, you can use Container/Presentation pattern.
In the Container/Presentation pattern, you have a ContainerComponent which is responsible to do requests, handle callbacks, and provide data to the Presentation component, which would be responsible to just render things.
If you don't want to use this pattern, at least put this fetch call in componentDidMount method.
Thank you all for your suggestions, duly noted and appreciated.
#Witalo-Benicio #Andris-laduzans #Drew-reese
I've fixed it, by changing
class SearchMarketActivity extends Component {
static navigationOptions = {
headerShown: false,
cardStyle: {
backgroundColor: 'white'
}
}
render() {
return(
<View>
<StatusBar
animated={true}
backgroundColor="#585858"
barStyle={'light-content'}
showHideTransition={'slide'}
hidden={false} />
<SearchMarket navigation={this.props.navigation} />
</View>
)
}
}
to
class SearchMarketActivity extends Component {
static navigationOptions = {
headerShown: false,
cardStyle: {
backgroundColor: 'white'
}
}
render() {
return(
<SearchMarket navigation={this.props.navigation} />
)
}
}
After, I added the StatusBar to the <SearchMarket /> component being imported
No matter what I try I can't seem to get my component to update. Below is my code:
import React, { Component } from "react";
import { StyleSheet, View, TouchableOpacity, Image, Text } from "react-native";
import PropTypes from "prop-types";
import { toggleSound } from "logic/Music.js";
import soundOn from "assets/img/swipe.png";
import soundOff from "assets/img/illum-triangle.png";
export default class GlobalSoundButton extends Component {
constructor(props) {
super(props);
this.state = {
audioOff: this.props.audioOff
};
}
componentDidUpdate = prevProps => {
if (prevProps.audioOff !== this.props.audioOff) {
this.setState({ audioOff: this.props.audioOff }, () => {
console.log("SWITCHING STATE", this.state);
};
this.forceUpdate();
}
};
render() {
return (
<View style={styles.soundButtonWrapper} key={this.props.name}>
<TouchableOpacity
style={styles.soundButtonPress}
activeOpacity={0.8}
onPress={() => {
toggleSound(this.props.sounds);
}}
>
<Image
style={styles.soundButton}
source={this.state.audioOff ? soundOff : soundOn}
></Image>
<Text style={styles.text}>
Audio {this.state.audioOff ? "off" : "on"}
</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
soundButtonWrapper: {
width: "100%",
height: 40,
position: "absolute",
top: 20
},
soundButtonPress: {
width: 40,
height: 40,
position: "absolute",
right: 20
},
soundButton: {
width: 40,
height: 40,
tintColor: "#59BC92"
},
text: {
width: 120,
height: 40,
position: "absolute",
right: 80,
color: "white"
}
});
GlobalSoundButton.propTypes = {
sounds: PropTypes.array
};
The weird part is that I'm using this component in two different screens and in one case it works absolutely fine but in the other screen it doesn't re-render when the audio is changed.
Every time I press it the props get updated so I KNOW that the state updates. The console log always writes out different state so I know the state is updated. I also tried forceUpdate() and adding a key (you can see the key in the code) but it doesn't help.
I'm at a loss here. Has anyone bumped into the same issue or can help? I also know there are several questions quite like this one but no one answered my questions so I had to write my own.
I removed a lot of unrelated code here but basically I pass the props down the following way in my HomeScreen (it's the GlobalSoundButton, and in this case it works):
import GLOBAL from "logic/GlobalState.js";
export default class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
audioOff: false,
};
}
async componentDidMount() {
let audioOff;
try {
audioOff = await retrieveData("audioOff");
} catch (err) {
console.error(err);
}
this.setState(
{
audioOff: audioOff === "true"
});
render() {
GLOBAL.homeScreen = this;
return (
<View style={styles.container}>
<View style={styles.bg}>
<ImageBackground
source={pattern}
style={styles.img}
resizeMode="repeat"
imageStyle={styles.innerImg}
/>
</View>
<View style={styles.container}>
<GlobalSoundButton
sounds={this.state.allSounds}
audioOff={this.state.audioOff}
name="HomeScreenKey"
></GlobalSoundButton>
<Text
style={[styles.smallText, styles.ppText]}
onPress={() =>
Linking.openURL(
"https://privacy-illuminati-flip.netlify.com"
).catch(err => console.error("An error occurred", err))
}
>
Privacy Policy
</Text>
<Text style={styles.welcome}>Illuminati Flip</Text>
<TouchableWithoutFeedback
onPressIn={evt => this.grabFinger(evt)}
onPressOut={evt => this.swipe(evt)}
pressRetentionOffset={{ top: 100, left: 0, bottom: 100, right: 0 }}
>
<View style={styles.swipeArea}>
<View style={styles.gridSelection}>
<Animated.View
pointerEvents={"none"}
style={{
...styles.innerGridSelection,
transform: [{ translateX: this.springValue }]
}}
>
{difficulties}
</Animated.View>
</View>
<View style={styles.margin}>
<Animated.Image
source={swipeFinger}
style={{
...styles.hand,
transform: [
{
translateX: this.fingerValue.interpolate({
inputRange: [-1, 1],
outputRange: [-15, 15]
})
}
]
}}
/>
<Text style={styles.smallText}>Swipe to change difficulty</Text>
</View>
</View>
</TouchableWithoutFeedback>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
this.checkTutorial();
}}
>
<Text style={styles.button}>Begin Ceremony</Text>
</TouchableOpacity>
<Text
style={[styles.smallText, styles.musicText]}
onPress={() =>
Linking.openURL("https://fanlink.to/triobgame").catch(err =>
console.error("An error occurred", err)
)
}
>
Music by #triobelisk
</Text>
<Text
style={[styles.smallText, styles.devText]}
onPress={() =>
Linking.openURL("https://twitter.com/iamjohnhult").catch(err =>
console.error("An error occurred", err)
)
}
>
Developed by #iamjohnhult
</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
...
});
The difference in my other component where it doesn't work I render it like this:
import GLOBAL from "logic/GlobalState.js";
<GlobalSoundButton
sounds={this.state.allSounds}
audioOff={GLOBAL.homeScreen.state.audioOff}
name="GameScreenKey"
></GlobalSoundButton>
And the GlobalState.js is as follows:
module.exports = {
homeScreen: null
};
I have created custom tabs in react-native but I am unable to select a tab. I have initialized the state for the selected tab but do not know where to set the state.
here is my code:
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
Image,
View
} from 'react-native';
var Dimensions = require('Dimensions');
var windowSize = Dimensions.get('window');
var bg = require('image!bg');
class TabView extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'list',
selectedTab: 'map'
};
}
render() {
return (
<View style={styles.container}>
<Image style={styles.bg} source={bg} />
<View style={styles.tabView}>
<View style={[styles.listView,styles.selectedView]}>
<Text>List View</Text>
</View>
<View style={[styles.listView,{}]}>
<Text>Map View</Text>
</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
bg: {
position: 'absolute',
left: 0,
top: 0,
width: windowSize.width,
height: windowSize.height
},
tabView: {
flexDirection: 'row',
//bottom: 200,
borderWidth:2,
borderColor:'rgba(4, 193, 3,1)',
borderRadius: 5,
marginHorizontal: 20,
marginTop: 50
},
listView: {
flex: 2,
padding:7,
alignItems:'center'
},
mapView: {
flex: 2,
padding:7,
alignItems:'center'
},
selectedView: {
backgroundColor:'rgba(4, 193, 3,1)',
color: 'white'
}
});
module.exports = TabView
I just want to know where shall I add a check so that I can make a difference in the selected tab
Any help will be appreciated.
Please, check out the code here, to get an idea how it can be done
const Tab = (props) => {
let style = props.isSelected && styles.selectedTab || styles.normalTab;
return (
<View style={style}>
<TouchableHighlight onPress={() => props.onTabPress(props.id)}>
<Text>{props.title}</Text>
</TouchableHighlight>
</View>
)
}
class TabsView extends Component {
constructor(props) {
super(props)
this.state = {
selectedTab: 'one'
}
}
render() {
return (
<View>
<Tab onTabPress={this.onSelectTab.bind(this)} title="One" id="one" isSelected={this.state.selectedTab == "one"}/>
<Tab onTabPress={this.onSelectTab.bind(this)} title="Two" id="two" isSelected={this.state.selectedTab == "two"}/>
</View>
)
}
onSelectTab(selectedTab) {
this.setState({ selectedTab })
}
}
The above code splits your component in two parts, a logical part (TabsView) and a dumb presentational part (Tab)
The logical handles the clickHandler (onSelectTab) which is passed as a prop (onTabPress) to the dumb (Tab) Component.
I just want to know where shall I add a check so that I can make a difference in the selected tab
In the render method, it should go
example:
render() {
let FirstTabStyles = Object.assign(
defaultTabStyles,
(isFirstSelected && selectedStyles || {})
)
let SecondTabStyle = Object.assign(
defaultTabStyles,
(isSecondSelected && selectedStyles || {})
)
return (
<View>
<FirstTab style={FirstTabStyle} />
<SecondTab style={SecondTabStyle} />
</View>
)
}
In React, it seems like an animation is always bound to a property in this.state. But what if I have multiple objects in a view that need to be animated? For example what if I have a ListView with multiple Images and I want to animate the opacity of those images as they load into the ListView?
render() {
//var _scrollView: ScrollView;
return (
<View style={styles.container}>
<ScrollView
style={styles.scrollView}>
<ListView
initialListSize={1}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
style={styles.postList}
/>
</ScrollView>
</View>
);
}
renderRow(post) {
//var postItemHeight = windowSize / 2
return (
<View style={styles.postItem}>
<Image
style={styles.postImage}
source={{uri: post.cover_image}}
onLoad={(e) => this.imageLoaded()}>
</Image>
</View>
);
}
imageLoaded() {
// now animate the image opacity
// for every image that is loaded into the listview
}
There is no reason the Animated value needs to live in component state - that's just how the examples show how to do it. If you wanted, you can keep an array of Animated values in the state, put them in your Flux store, or however you want to do it.
In your particular case, however, the easiest solution would be create a component that represents a single image row of your ListView. You can then use that component's individual state to manage its animation.
For example:
const FadeImage = React.createClass({
displayName: 'FadeImage',
propTypes: Image.propTypes,
getInitialState() {
return {
opacity: new Animated.Value(0)
};
},
setNativeProps(nativeProps) {
this._image.setNativeProps(nativeProps);
},
fadeIn() {
Animated.spring(this.state.opacity, {
toValue: 1,
friction: 10,
tension: 60
}).start();
},
render() {
return (
<Animated.View style={{opacity: this.state.opacity}}>
<Image {...this.props} onLoad={this.fadeIn} ref={component => this._image = component} />
</Animated.View>
);
}
});
It's a drop-in replacement for Image, so you can use it just as you would a regular Image component.