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})}
Related
I am trying to make a game in react-native. I want to render 200+ views on the Game screen. Each View has a pressable functionality. Whenever I press the View I need to run a function that will change the View background color and update score on the game context. But Whenever I try to press any View it took some time to change the background and update the context.
Note
I am using the expo as a development environment and I am using a real device too.
My View Component
import { useEffect, useState, memo } from "react";
import { useContext } from "react";
import { gameContext } from "./gameContext";
import { Pressable, View } from "react-native";
function CheckBoxCom() {
const [active, setActive] = useState(false);
const { score, setScore } = useContext(gameContext);
useEffect(() => {
let time = setTimeout(() => {
setActive(false);
}, Math.floor(Math.random() * 35000));
return () => clearTimeout(time);
}, [active]);
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score + 1);
};
return (
<View>
<Pressable onPress={handlePress}>
<View
style={{
width: 20,
height: 20,
borderWidth: 2,
borderColor: active ? "green" : "gray",
margin: 3,
borderRadius: 3,
backgroundColor: active ? "green" : null,
}}
></View>
</Pressable>
</View>
);
}
export default memo(CheckBoxCom);
Game Screen Component
import { useContext, useEffect, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, FlatList } from "react-native";
import CheckBox from "./CheckBox";
import { gameContext } from "./gameContext";
export default function Game({ navigation }) {
const { score, time, setTime, boxList } = useContext(gameContext);
const [intervalId, setIntervalId] = useState("");
useEffect(() => {
const int = setInterval(() => {
setTime((prvTime) => prvTime - 1);
}, 1000);
setIntervalId(int);
return () => clearInterval(int);
}, []);
if (time === 0) {
clearInterval(intervalId);
navigation.navigate("Score", { score });
}
return (
<View style={{ flex: 1 }}>
<StatusBar style="auto" />
<View style={styles.textHeader}>
<Text>Score : {score}</Text>
<Text>Time Left: {time}s</Text>
</View>
<View style={styles.checkBoxContainer}>
<FlatList
style={{ alignSelf: "center" }}
data={boxList}
initialNumToRender={50}
numColumns={12}
renderItem={(i) => <CheckBox />}
keyExtractor={(i) => i.toString()}
scrollEnabled={false}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
textHeader: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
marginTop: 40,
paddingHorizontal: 30,
},
checkBoxContainer: {
margin: 20,
display: "flex",
flexWrap: "wrap",
height: "80%",
overflow: "hidden",
flexDirection: "row",
},
});
How can I run view function immediately whenever I press it?
The reason it is slow is that when you press on a view, all 200+ CheckBoxCom components rerender. If they don't need to, we can improve performance by trying to prevent those unnecessary rerenders.
I believe the major bottleneck here is the gameContext. It groups together a lot of states and if any of these were to change, all components will rerender. It provides score state that you are reading within each CheckBoxCom. Whenever the score changes all CheckBoxCom components will re-render. If you change handlePress() to:
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score => score + 1);
};
Please note the use of callback to update the score in the above handler. In this case, we don't need to read score from context, so we can remove it from the game context provider, only pass setScore. Removing score from the context provider is important because not doing so will rerender all components using the context even if you don't specifically destructure score.
Also, make sure you don't have a lot of state variables within a single context. Split it into multiple contexts if you have different states in there. In this way, you will be able to reduce unnecessary rerenders of the CheckBoxCom components.
Since your CheckBoxCom components have an internal state, using React.memo() will not help to prevent rerenders because it only works for rerenders resulting from changed props.
But if you are able to refactor them to lift the active state up to the parent i.e. something like activeViews or something (which could be a map of indexes which are true i.e. active), then you can pass the active state as a boolean prop to each CheckBoxCom component. And if we also pass setScore via a prop instead of via context, we can benefit from React.memo(). BTW it is not necessary to wrap setState methods with useCallback().
The end result will be: CheckBoxCom components with zero internal states and no reliance on context, in other words, pure components i.e. components which work nicely with React.memo().
Use pagination in flatlist
for ref: Pagination in flatlist
import React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
Platform,
ActivityIndicator,
} from 'react-native';
export default class App extends Component {
constructor() {
super();
this.state = {
loading: true,
//Loading state used while loading the data for the first time
serverData: [],
//Data Source for the FlatList
fetching_from_server: false,
//Loading state used while loading more data
};
this.offset = 0;
//Index of the offset to load from web API
}
componentDidMount() {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset : "+this.offset);
console.log(responseJson.slice((this.offset*12),((this.offset+1)*12)-1));
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
// serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
//adding the new data with old one available in Data Source of the List
loading: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
}
loadMoreData = () => {
//On click of Load More button We will call the web API again
this.setState({ fetching_from_server: true }, () => {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset Load : "+this.offset);
console.log(responseJson);
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
//serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
fetching_from_server: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
});
};
renderFooter() {
return (
//Footer View with Load More button
<View style={styles.footer}>
<TouchableOpacity
activeOpacity={0.9}
onPress={this.loadMoreData}
//On Click of button calling loadMoreData function to load more data
style={styles.loadMoreBtn}>
<Text style={styles.btnText}>Loading</Text>
{this.state.fetching_from_server ? (
<ActivityIndicator color="white" style={{ marginLeft: 8 }} />
) : null}
</TouchableOpacity>
</View>
);
}
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator size="large" />
) : (
<FlatList
style={{ width: '100%' }}
keyExtractor={(item, index) => index}
data={this.state.serverData}
renderItem={({ item, index }) => (
<View style={styles.item}>
<Text style={styles.text}>
{item.currency}
{'.'}
{item.code}
</Text>
</View>
)}
onEndReached={this.loadMoreData}
onEndReachedThreshold ={0.1}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListFooterComponent={this.renderFooter.bind(this)}
//Adding Load More button as footer component
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 30,
},
item: {
padding: 10,height:80
},
separator: {
height: 0.5,
backgroundColor: 'rgba(0,0,0,0.4)',
},
text: {
fontSize: 15,
color: 'black',
},
footer: {
padding: 10,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
loadMoreBtn: {
padding: 10,
backgroundColor: '#800000',
borderRadius: 4,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
btnText: {
color: 'white',
fontSize: 15,
textAlign: 'center',
},
});
Been using Expo and RN on an app, but I'm stuck with a problem. I'm using Expo Video
to make a photo/video gallery. The problem is, if I have more than 1 video in the array, only the last one will play (problem with useRef). And I couldn't find a way to create a ref for each one of them.
Solutions that I've tried and half worked: I created a VideoComponent (as a function then added on return), and each component had its own useRef and useState for playing inside the component a different useRef/useState for video/status for each. It worked okay-ish. But the problem was when other states changed (user presses like, for example). Whenever a state changes, and rerenders, the whole video reset to the beginning. Which is not ok.
The video reset on state change of other components doesn't affect the video if doing it normally (one useRef/state) but as I said, It's only playing the last component, which is not okay.
import React, { useRef, useState } from 'react';
import {
SafeAreaView,
View,
FlatList,
StyleSheet,
Text,
StatusBar,
} from 'react-native';
function App(props) {
const [allData, setAllData] = useState([
{
medias: [
{ link: 'https://link.com/link1.avi', mediaExtension: 'avi' },
{ link: 'https://link.com/link2.jpg', mediaExtension: 'jpg' },
{ link: 'https://link.com/link3.mov', mediaExtension: 'mov' },
],
name: 'Name',
description: 'description',
},
]);
const video = useRef(null);
const [status, setStatus] = useState({});
return (
<View style={{}}>
<FlatList
horizontal
data={allData}
renderItem={({ item }) => (
<View style={{}}>
{item.medias.map((item) => (
<View>
{item.mediaExtension === 'mov' || 'avi' || 'WebM' ? (
<View style={{ flex: 1 }}>
<TouchableOpacity
onPress={() =>
video.isPlaying
? video.current.pauseAsync()
: video.current.playAsync()
}>
<Video
ref={video}
style={{ alignSelf: 'center' }}
source={{
uri: item.link,
}}
onPlaybackStatusUpdate={(status) =>
setStatus(() => status)
}
/>
</TouchableOpacity>
</View>
) : (
<Image style={{}} source={{ uri: item.link }} />
)}
</View>
))}
</View>
)}
/>
</View>
);
}
export default App;
As far as I understand, you want to create a FlatList of Videos, and onScroll you want to pause it. This can be implemented as shown below
Also, here is a Working Example for this
import * as React from 'react';
import { Text, View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
import VideoPlayer from './components/VideoPlayer';
const Videos = [
{
_id: 1,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 2,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 3,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 4,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 5,
source: require('./assets/videoplayback.mp4'),
},
{
_id: 6,
source: require('./assets/videoplayback.mp4'),
},
];
export default function App() {
const [Viewable, SetViewable] = React.useState([]);
const ref = React.useRef(null);
const onViewRef = React.useRef((viewableItems) => {
let Check = [];
for (var i = 0; i < viewableItems.viewableItems.length; i++) {
Check.push(viewableItems.viewableItems[i].item);
}
SetViewable(Check);
});
const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 80 });
return (
<View style={styles.container}>
<FlatList
data={Videos}
keyExtractor={(item) => item._id.toString()}
renderItem={({ item }) => <VideoPlayer {...item} viewable={Viewable} />}
ref={ref}
onViewableItemsChanged={onViewRef.current}
viewabilityConfig={viewConfigRef.current}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
});
and VideoPLayer component looks like this
import * as React from 'react';
import { Text, View, StyleSheet, Dimensions } from 'react-native';
import Constants from 'expo-constants';
import { Video, AVPlaybackStatus } from 'expo-av';
export default function VideoPlayer({ viewable, _id, source }) {
const video = React.useRef(null);
React.useEffect(() => {
if (viewable) {
if (viewable.length) {
if (viewable[0]._id === _id) {
video.current.playAsync();
} else {
video.current.pauseAsync();
}
} else {
video.current.pauseAsync();
}
} else {
video.current.pauseAsync();
}
}, [viewable]);
return (
<View style={styles.container}>
<Video
ref={video}
source={source}
rate={1.0}
volume={1.0}
resizeMode={'contain'}
isLooping
shouldPlay
style={styles.video}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
width: Dimensions.get('window').width,
marginBottom: 100,
marginTop: 100,
},
video: {
width: Dimensions.get('window').width,
height: 300,
},
});
I have an App in which there is a View with two CardViews that each direct me to different screens.
Now I have added another CardView, and there are already three.
The problem is that neither in iOS nor in Android the third of the CardView is shown in full, only part of it is shown as I show in the screenshot.
I have tried ScrollView since it is a list of few items and Scroll works, but it does not stop and when I release my finger from the screen, the screen returns to its initial position.
Also the scroll does the entire screen, including the Header, which should not be the case
I have also tried using FlatList, but I get errors
I have played with the View styles, but I do not understand the problem that is offered so that the Scroll does not work within the View.
I show the code of the file, with the third of the CardView added.
How can I correct this and make the screen Scroll to be able to access all the CardViews on the screen?
import React, { Component } from "react";
import _ from "lodash"
import ToolBar from "../../../component/ToolBar";
import styleApp from "../../../../res/style/style";
import AppText from "../../../component/Text";
import { strings } from "../../../config/i18n/i18n";
import CardView from "../../../component/CardView";
import { sizeWidth } from "../../../util/Size";
import NavigationActions from "../../../router/NavigationActions"
import {
AsyncStorage,
Image,
StyleSheet,
View,
FlatList,
ScrollView,
List,
SectionList
} from "react-native"
export default class PaintScreen extends Component {
state = {
isLoading: true,
studiedAlphabet: [],
studiedNumber: []
};
componentDidMount() {
AsyncStorage.getAllKeys((error, keys) => {
AsyncStorage.multiGet(keys, (error, stores) => {
const alphabets = [];
const numbers = [];
stores.map((result, i, store) => {
// get at each store's key/value so you can work with it
if (store[i] != null) {
//data provide is [key: value] - value [[]]
if (store[i][0] == "learned_alphabets") {
alphabets.push(JSON.parse(store[i][1]));
}
if (store[i][0] == "learned_numbers") {
numbers.push(JSON.parse(store[i][1]));
}
}
if (i == keys.length - 1) {
this.setState({
isLoading: false,
studiedAlphabet: alphabets[0],
studiedNumber: numbers[0]
});
}
});
});
});
}
render() {
if (!this.state.isLoading) {
return (
<View style={styles.containerMio}>
<Image
style={styles.backgroundImage}
source={require("../../../../res/images_paint/background/backgroundA_2x.png")}
/>
<Image
style={styles.newPaintImage}
source={require("../../../../res/images_paint/mainScreen/new_paint_ball_2x.png")}
/>
{this.renderToolbar()}
<View
style={{
width: "100%",
alignItems: "center",
position: "absolute",
top: sizeWidth(20)
}}
>
{this.renderAnimal()}
{this.renderObject()}
{this.renderAnimal()}
</View>
</View>
)
} else {
return null;
}
}
renderToolbar = () => {
return (
<ToolBar
center={
<AppText style={styleApp.ToolBarText}>
{strings("paint.title")}
</AppText>
}
/>
);
};
renderAnimal = () => {
return (
<CardView
image={require("../../../../res/images_paint/newPaintingScreen/animals_2x.png")}
styleImg={styles.styleAnimal}
title={strings("paint.animals")}
backgroundColor={"#fff989"}
colorButton={"#FE6230"}
onPress={() =>
NavigationActions.navigate("PaintAnimals", {
studiedNumber: this.state.studiedNumber,
studiedAlphabet: this.state.studiedAlphabet
})
}
/>
);
}
renderObject = () => {
const { studiedAlphabet } = this.state;
return (
<CardView
image={require("../../../../res/images_paint/newPaintingScreen/object_2x.png")}
styleImg={styles.styleObject}
title={strings("paint.objects")}
backgroundColor={"#B29FFF"}
colorButton={"#FE6230"}
onPress={() =>
NavigationActions.navigate("PaintObjects", {
studiedNumber: this.state.studiedNumber,
studiedAlphabet: this.state.studiedAlphabet
})
}
/>
);
}
renderAnimal = () => {
return (
<CardView
image={require("../../../../res/images_paint/newPaintingScreen/animals_2x.png")}
styleImg={styles.styleAnimal}
title={strings("paint.animals")}
backgroundColor={"#fff989"}
colorButton={"#FE6230"}
onPress={() =>
NavigationActions.navigate("PaintAnimals", {
studiedNumber: this.state.studiedNumber,
studiedAlphabet: this.state.studiedAlphabet
})
}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column'
},
containerMio: {
flex: 1,
flexDirection: "column"
},
backgroundImage: {
resizeMode: "stretch",
position: "absolute",
width: "100%",
height: "100%"
},
newPaintImage: {
position: "absolute",
width: "100%",
height: "100%"
},
styleAnimal: {
resizeMode: "center",
width: sizeWidth(70),
height: sizeWidth(50),
marginLeft: sizeWidth(-5),
},
styleObject: {
resizeMode: "center",
width: sizeWidth(70),
height: sizeWidth(50),
marginLeft: sizeWidth(-5),
}
})
So I was making this movie browser project in which I had to fetch data from OMDb API(http://www.omdbapi.com/) and display the data in a Flatlist component.
Although I managed to display 10 results of every movie searched for(as API return 10 items on every call), I added a button that would run a function to send a request again to the API using the page parameter, fetch 10 more results and concatenate the results to the movies.
But as soon as I press the button and run the function, this error appears undefined is not an object (evaluating 'item.Title').
This is my code for the Home Component => Home.js
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text, FlatList, TouchableHighlight, Image, Button} from 'react-native';
import { TextInput } from 'react-native-gesture-handler';
import { fetchMovies } from "../api/api";
export default class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
movies: null,
};
}
//update search text
componentDidUpdate(prevState) {
if (this.state.text !== prevState.text) {
this.getSearch(this.state.text);
}
}
//get search results from fetchMovies
getSearch = async text => {
const results = await fetchMovies(text)
this.setState({ movies: results });
};
////////////////////////////Function making API call using the page parameter///////////
//loading more movies
handleLoadMore = async() => {
try {
const page = Math.trunc(this.state.movies.length / 10) + 1;
const res = await fetchMovies(this.state.text, page);
this.setState(prevState => ({
movies: prevState.movies.concat(res.movies)
}))
}catch(err){
console.log(err.message);
}
}
//////////////////////////////////////////////////////////
//movie title and poster to render in the flatlist
movieCard = ({ item }) => {
return (
<TouchableHighlight
style={styles.movieCard}
underlayColor="white"
onPress={() => {
this.props.navigation.navigate("Details", {
title: item.title,
id: item.imdbID
});
}}
>
<View>
<Image
style={styles.movieImage}
source={ {uri: item.Poster} }
/>
<View style={{alignItems: 'center'}}>
<Text style={styles.movieTitle}>{item.Title} ({item.Year})</Text>
</View>
</View>
</TouchableHighlight>
);
};
render() {
return(
<View style={styles.container}>
<TextInput
style={styles.searchBox}
autoCorrect={false}
autoCapitalize='none'
autoFocus maxLength={45}
placeholder='Search'
onChangeText={(text) => this.setState({ text })}
value={this.state.text}
/>
{this.state.movies ?
<FlatList
style={styles.movieList}
data={this.state.movies}
renderItem={this.movieCard}
keyExtractor={item => item.Title + item.imdbID}
/>
:
<Text
style={{
alignSelf: 'center',
paddingTop: 150,
justifyContent: 'center',
color: '#8a8787',
fontStyle: 'italic' }}>
search for a movie above...
</Text>
}
<Button
onPress={this.handleLoadMore}
title="Load More"
color="#841584"
/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#DDD',
alignItems: 'center',
},
searchBox: {
fontSize: 20,
fontWeight: '300',
padding: 10,
width: '100%',
backgroundColor: 'white',
borderRadius: 10,
marginBottom: 30
},
movieList: {
flex: 1,
marginHorizontal: 30,
},
movieCard: {
flex: 1,
margin: 5,
padding: 5,
},
movieImage: {
width: '100%',
height: 350,
borderRadius: 10,
alignSelf: 'center',
},
movieTitle: {
marginTop: 10,
fontSize: 20,
color: '#333'
}
});
This is the code for api functions => api.js
const API_KEY = "API_KEY";
//fetch search data from omdb api
export const fetchMovies = async (response, page) => {
const url = `http://www.omdbapi.com/?apikey=${API_KEY}&s=${response}`;
try {
let response = await fetch(url);
if(page) {
response = await fetch(url + `&page=${page}`)
}
const { Search } = await response.json();
return Search;
} catch (err) {
return console.log(err);
}
};
//fetch ID from omdb api
export const fetchById = async id => {
const url = `http://www.omdbapi.com/?apikey=${API_KEY}&i=${id}`;
try {
const response = await fetch(url);
const results = await response.json();
return results;
} catch (err) {
return console.log(err);
}
};
I know solution to this is probably simple but being new to react-native I am not able to figure it out.
regarding FlatList, In docs they apparently pass lambda which returns rendered items to renderItem prop
Also your initialized state.movies is null and i think it should be an empty array
this.state = {
text: "",
movies: [],
};
<FlatList
style={styles.movieList}
data={this.state.movies}
renderItem={({item}) => this.movieCard({item})}
keyExtractor={item => item.Title + item.imdbID}
I have a list of articles which is fetching from server(json).There will be two server calls.What I meant is now I'm listing some article title (fetching from server1) within a Card.Below that there will be an add button for copy and pasting new article links(that will be saved to a different server).So I'm trying to append this newly added articles to my existing list(just like the same in pocket app).How can I do this?I've tried something like the below.But I'm getting error , may be a little mistake please help me to figure out.Since the render should happen only after button click(for viewing newly added ones).So state will also set after that right?How can I do that?
import {article} from './data'; //json
import AddNewarticle from './AddNewarticle';
class SecondScreen extends Component {
state= {
newarticle: []
};
// request for saving newly added article
onPressSubmit(){
fetch('www.mywebsite.com',{
method: 'POST',
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: this.state.url,
})
})
.then(response => response.json())
.then((responseData) => this.setState({newarticle: responseData}));
}
renderArticle(){
this.state.newarticle.message.map(newlist =>
<AddNewarticle key ={newlist.title} newlist={newlist} />
);
}
render(){
return(
<ScrollView>
{article.map(a =>
<CardSection>
<Text>
{a.title}</Text>
</CardSection>
{this.renderArticle()}
</ScrollView>
<Icon
raised
name="check"
type="feather"
color="#c1aba8"
iconStyle={{ resizeMode: "contain" }}
onPress={() => {
this.onPressSubmit();
}}
/>
);
}
Also my newarticle (json) is as follows.The response I'm getting from server after submitting new articles.
{
"message": {
"authors": [
"Zander Nethercutt"
],
"html": "<div><p name=\"c8e3\" id=\"c8e3\" class=\"graf graf--p graf--hasDropCapModel graf--hasDropCap graf--leading\">The presence of advertising in This is the power of branding.",
"title": "The Death of Advertising – Member Feature Stories – Medium"
},
"type": "success"
}
The error I'm getting is newarticle is not defined.
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, ActivityIndicator, ListView, Text, View, Alert,Image, Platform } from 'react-native';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true
}
}
GetItem (flower_name) {
Alert.alert(flower_name);
}
componentDidMount() {
return fetch('https://reactnativecode.000webhostapp.com/FlowersList.php')
.then((response) => response.json())
.then((responseJson) => {
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
isLoading: false,
dataSource: ds.cloneWithRows(responseJson),
}, function() {
// In this block you can do something with new state.
});
})
.catch((error) => {
console.error(error);
});
}
ListViewItemSeparator = () => {
return (
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.MainContainer}>
<ListView
dataSource={this.state.dataSource}
renderSeparator= {this.ListViewItemSeparator}
renderRow={(rowData) =>
<View style={{flex:1, flexDirection: 'row'}}>
<Image source = {{ uri: rowData.flower_image_url }} style={styles.imageViewContainer} />
<Text onPress={this.GetItem.bind(this, rowData.flower_name)} style={styles.textViewContainer} >{rowData.flower_name}</Text>
</View>
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
MainContainer :{
// Setting up View inside content in Vertically center.
justifyContent: 'center',
flex:1,
margin: 5,
paddingTop: (Platform.OS === 'ios') ? 20 : 0,
},
imageViewContainer: {
width: '50%',
height: 100 ,
margin: 10,
borderRadius : 10
},
textViewContainer: {
textAlignVertical:'center',
width:'50%',
padding:20
},
tabIcon: {
width: 16,
height: 16,
}
});
AppRegistry.registerComponent('App', () => App);
export default App;