I am new to react native and I was implemented a simple idea in my head. Basically, I am doing a 'todo list' like component which theres a add button below and items can be added. The problem arises after clicking on the add button and the list gets updated and the following xcode warning message appears. And I have realised, after implementing the ListView, that the app in the simulator slows down so much i couldn't even inspect. The alert popup would freeze the UI after some text are entered too, and the entire app needs to be built again since I couldn't do anything. Thanks for all the help!
Main component: SurveyQn
'use strict'
import React, {
Component,
StyleSheet,
Text,
TouchableHighlight,
TextInput,
View,
ListView,
AlertIOS
} from 'react-native';
var LongButton = require('./LongButton.js');
class SurveyQn extends Component {
constructor(props) {
super(props);
this.state = {
options: [{option: 'Pizza'}],
};
}
componentWillMount() {
this.dataSource = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
})
}
_renderItem(item) {
return (
<LongButton
text={item.option}
onPress={() => {}}
//btnViewStyle={styles.buttonView}
//btnTextStyle={styles.buttonText}
/>
);
}
_addItem() {
AlertIOS.alert(
'Add new option',
null,
[
{
text: 'Add',
onPress: (text) => {
var options = this.state.options;
options.push({option: text})
this.setState({ options: options})
}
},
],
'plain-text'
);
}
render(){
var dataSource = this.dataSource.cloneWithRows(this.state.options);
return (
<View style={styles.container}>
<TextInput
style={styles.question}
placeholder="Question title"
placeholderTextColor="#4B667B"
selectionColor="#4B667B"
onChangeText={(text) => this.setState({text})}/>
<View style={styles.listView}>
<ListView
dataSource={dataSource}
renderRow={this._renderItem.bind(this)}/>
</View>
<TouchableHighlight
onPress={this._addItem.bind(this)}
style={styles.buttonView}
underlayColor='rgba(0,0,0,0)'>
<Text style={styles.buttonText}>
Add option
</Text>
</TouchableHighlight>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
width: 300,
flex :1,
},
listView: {
flex: 1,
},
question: {
height: 30,
fontSize: 20,
fontWeight: "100",
color: '#4B667B',
marginTop: 10,
marginBottom: 10,
},
buttonView: {
width: 300,
paddingVertical: 9,
borderWidth: 1,
borderColor: '#F868AF',
marginBottom: 13,
},
buttonText: {
textAlign: 'center',
fontSize: 25,
color: '#F868AF',
fontWeight: '500'
},
});
ListView item: LongButton
'use strict'
import React, {
Component,
StyleSheet,
Text,
TouchableHighlight,
View,
} from 'react-native';
class LongButton extends Component {
render(){
return (
<TouchableHighlight
onPress={this.props.onPress}
style={this.props.btnViewStyle}
underlayColor='rgba(0,0,0,0)'>
<Text style={this.props.btnTextStyle}>
{this.props.text}
</Text>
</TouchableHighlight>
);
}
}
module.exports = LongButton;
Xcode warning message upon adding item on alert
app[27881:11151280] the behavior of the UICollectionViewFlowLayout is not defined because:
app[27881:11151280] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
app[27881:11151280] The relevant UICollectionViewFlowLayout instance is <_UIAlertControllerCollectionViewFlowLayout: 0x7ff0685b1770>, and it is attached to ; layer = ; contentOffset: {0, 0}; contentSize: {0, 0}> collection view layout: <_UIAlertControllerCollectionViewFlowLayout: 0x7ff0685b1770>.
2016-04-06 07:50:01.545 decisionapp[27881:11151280] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
Updates:
I tried this but its not working either. Could it be the alert causing these problems? Its just taking forever to render the alert after clicking on the btn.
class SurveyQn extends Component {
constructor(props) {
super(props);
this.state = {
options: [{option: 'Pizza'}],
dataSource : new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
})
};
}
componentWillMount() {
var data = this.state.options;
this.state.dataSource.cloneWithRows(data);
}
_renderItem(item) {
return (
{item.option}
);
}
_addItem() {
AlertIOS.alert(
'Add new option',
null,
[
{
text: 'Add',
onPress: (text) => {
var options = this.state.options;
options.push({option: text})
this.setState({ options: options})
}
},
],
'plain-text'
);
}
render(){
return (
<View style={styles.listView}>
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderItem.bind(this)}/>
</View>
<TouchableHighlight
onPress={this._addItem.bind(this)}
style={styles.buttonView}
underlayColor='rgba(0,0,0,0)'>
<Text style={styles.buttonText}>
Add option
</Text>
</TouchableHighlight>
</View>
);
}
}
Before diving into complex EcmaScript notation, you can use simple notation. Here is a simple example of ListView. Please go through it and understand how it works.
var API = require('./API');
module.exports = React.createClass({
getInitialState: function(){
return {
rawData: [],
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
}),
loaded: false,
}
},
componentWillMount: function(){
this.loadData();
},
loadData: function(){
API.getItems()
.then((data) => {
this.setState({
rawData: this.state.rawData.concat(data),
dataSource: this.state.dataSource.cloneWithRows(data),
loaded: true,
});
});
},
render: function(){
return(
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderItem}
style={styles.listView}>
</ListView>
);
},
renderItem: function(item){
return (
<View>
<Text>Custom Item</Text>
</View>
);
},
}
In API.js, I am fetching data from an API.
getItems: function(){
var REQUEST_URL = 'http://api.example.org/item/get?api_key=xxxx;
return fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
return responseData.results.sort(sortByDate);
});
}
The code may not work, since I have not tested. But you can refer my sample project on github. Repo
Related
I am trying to implement a list of custom card view's(Like the CollectionView in ios with custom collectionview cell). How can I achieve this any idea's or tutorials plz. I do not want to use third party. Could someone help me out.
You can have a Collection View like a solution with React Native FlatList. To make a grid view you have to use numColumns prop. The following code segment will support you.
class MyListItem extends React.PureComponent {
render() {
return (
<View style={styles.item}>
<Text>
{this.props.title}
</Text>
</View>
);
}
}
export default class App extends React.Component {
data = [
{
"id":1,
"label":"Label 01"
},
{
"id":2,
"label":"Label 02"
},
{
"id":3,
"label":"Label 03"
},
{
"id":4,
"label":"Label 04"
},
{
"id":5,
"label":"Label 05"
},
{
"id":6,
"label":"Label 06"
},
{
"id":7,
"label":"Label 07"
},
{
"id":8,
"label":"Label 08"
},
{
"id":9,
"label":"Label 09"
}
]
_keyExtractor = (item, index) => item.id;
renderItem = ({item}) => (
<MyListItem
id={item.id}
title={item.label}
/>
);
render() {
return (
<View style={styles.container}>
<FlatList
data={this.data}
renderItem={this.renderItem}
keyExtractor={this._keyExtractor}
numColumns={3}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
item: {
backgroundColor: '#add8e6',
alignItems: 'center',
justifyContent: 'center',
flex:1,
margin: 5,
height: 100
}
});
Depends on what you are trying to do. In minimum, a FlatList expects the following props;
data: An array of data. (Each data point is one row in your list)
renderItem: How to transform a data point into a row. (Think of your render function, but for just a row.)
keyExtractor: How to create keys(think of them as ids) for these rows to keep track of them.
In the end you would need to do the following (keep in mind data structure, keyExtractor and what is being rendered is completely arbitrary);
<FlatList
data={[{ id: 1, text: 'Hello' }, { id: 2, text: 'StackOverflow!' }]}
keyExtractor={item => `texts#${item.id}`}
renderItem={({ item }) => (<Text>{item.text}</Text>)}
/>
More extensive data about this can be found at official React Native documentation.
With react-native, I implemented IOS CameraRoll that fetches 300 images from 'Camera Roll' Album on first and keep fetching 300 images whenever scroll reaches the end. Below is My code. SalmonCameraRoll.js
import React from 'react'
import {
View,
Text,
TouchableHighlight,
Modal,
StyleSheet,
Button,
CameraRoll,
Image,
Dimensions,
ScrollView,
FlatList,
} from 'react-native'
import Share from 'react-native-share';
import RNFetchBlob from 'react-native-fetch-blob';
let styles
const { width, height } = Dimensions.get('window')
const fetchAmount = 300;
class SalmonCameraRoll extends React.Component {
static navigationOptions = {
title: 'Salmon Camera Roll',
}
constructor(props) {
super(props);
this.state = {
photos: [],
// index: null,
lastCursor: null,
noMorePhotos: false,
loadingMore: false,
refreshing: false,
};
this.tryGetPhotos = this.tryGetPhotos.bind(this);
this.getPhotos = this.getPhotos.bind(this);
this.appendPhotos = this.appendPhotos.bind(this);
this.renderImage = this.renderImage.bind(this);
this.onEndReached = this.onEndReached.bind(this);
this.getPhotos({first: fetchAmount, assetType: 'Photos'});
}
componentDidMount() {
this.subs = [
this.props.navigation.addListener('didFocus', () => {
this.getPhotos({first: fetchAmount, assetType: 'Photos'});
}),
];
}
componentWillUnmount() {
this.subs.forEach(sub => sub.remove());
}
tryGetPhotos = (fetchParams) => {
if (!this.state.loadingMore) {
this.setState({ loadingMore: true }, () => { this.getPhotos(fetchParams)})
}
}
getPhotos = (fetchParams) => {
if (this.state.lastCursor) {
fetchParams.after = this.state.lastCursor;
}
CameraRoll.getPhotos(fetchParams).then(
r => this.appendPhotos(r)
)
}
appendPhotos = (data) => {
const photos = data.edges;
const nextState = {
loadingMore: false,
};
if (!data.page_info.has_next_page) {
nextState.noMorePhotos = true;
}
if (photos.length > 0) {
nextState.lastCursor = data.page_info.end_cursor;
nextState.photos = this.state.photos.concat(photos);
this.setState(nextState);
}
}
onEndReached = () => {
if (!this.state.noMorePhotos) {
this.tryGetPhotos({first: fetchAmount, assetType: 'Photos'});
}
}
renderImage = (photo, index) => {
return (
<TouchableHighlight
style={{borderTopWidth: 1, borderRightWidth: 1, borderColor: 'white'}}
key={index}
underlayColor='transparent'
onPress={() => {
this.props.navigation.navigate('Camera', { backgroundImageUri: photo.node.image.uri })
}
}
>
<Image
style={{
width: width/3,
height: width/3
}}
representation={'thumbnail'}
source={{uri: photo.node.image.uri}}
/>
</TouchableHighlight>
)
}
render() {
return (
<View style={styles.container}>
<View style={styles.modalContainer}>
<FlatList
numColumns={3}
data={this.state.photos}
initialNumToRender={fetchAmount}
onEndReachedThreshold={500}
onEndReached={this.onEndReached}
refreshing={this.state.refreshing}
onRefresh={() => this.tryGetPhotos({first: fetchAmount, assetType: 'Photos'})}
keyExtractor={(item, index) => index}
renderItem={({ item, index }) => (
this.renderImage(item, index)
)}
/>
</View>
</View>
)
}
}
styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
modalContainer: {
// paddingTop: 20,
flex: 1,
},
scrollView: {
flexWrap: 'wrap',
flexDirection: 'row'
},
shareButton: {
position: 'absolute',
width,
padding: 10,
bottom: 0,
left: 0
}
})
export default SalmonCameraRoll
Problem is that in circumstance of a lot of images(about 10000 images) in 'Camera Roll' album, each image component was loaded so slowly that it was also loaded too slowly when scrolling accordingly.
In other famous apps like Facebook or Instagram, it loads all images quickly at once without fetching whenever scroll reaches end.
How can i make my Image component load fast? Or best of all(if possible), how can i make my CameraRoll load all images quickly at once without fetching whenever scroll reaches end?
Thank you.
Here I am only running two API requests. The first one in the componentDidMount function works fine, but the second one labeled handleMatchFacts does not work. In short, Using React-Native I'm retrieving information from the API, mounting it to the page and then once the Touchablehighlight is clicked it is suppose to retrieve additional information from the API according to the 'id' that is passed in 'onPress'. I am able to console.log the json of the data in the second request, but for some reason when I setState with the new data and render it to the page in ListView, I get an error.
import React from 'react'
import { View, Text, StyleSheet, ListView, TouchableHighlight } from 'react-native'
export default class Main extends React.Component {
constructor() {
super();
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
matches: ds.cloneWithRows([]),
matchFacts: ds.cloneWithRows([])
};
this.handleShowMatchFacts.bind(this)
}
componentDidMount(){
fetch("http://api.football-api.com/2.0/matches?match_date=27.04.2017&to_date=27.04.2017&Authorization=565ec012251f932ea4000001fa542ae9d994470e73fdb314a8a56d76")
.then(res => res.json())
.then(matches => {
this.setState({
matches : this.state.matches.cloneWithRows(matches)
})
})
}
handleShowMatchFacts = id => {
console.log('match', id)
return fetch(`http://api.football-api.com/2.0/matches/${id}?Authorization=565ec012251f932ea4000001fa542ae9d994470e73fdb314a8a56d76`)
.then(res => res.json())
.then(matchFacts => {
console.log('match facts', matchFacts)
let selectedMatch = matchFacts;
this.setState({
matches : this.state.matches.cloneWithRows([]),
matchFacts : this.state.matchFacts.cloneWithRows(selectedMatch)
})
})
}
render() {
return (
<View style={styles.mainContainer}>
<Text
style={styles.header}>
Todays Matches</Text>
<ListView
style={styles.matches}
dataSource={this.state.matches}
renderRow={(matches) =>
<TouchableHighlight
onPress={() => this.handleShowMatchFacts(matches.id)}
underlayColor="green"
><Text style={styles.item}> {matches.localteam_name} {matches.localteam_score} - {matches.visitorteam_score} {matches.visitorteam_name} </Text>
</TouchableHighlight>
}
/>
<ListView
style={styles.matches}
dataSource={this.state.matchFacts}
renderRow={(match) =>
<Text style={styles.item}> {match.localteam_name} {match.localteam_score} - {match.visitorteam_score} {match.visitorteam_name} </Text>
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
mainContainer : {
flex: 1,
padding: 20
},
header : {
textAlign: 'center'
},
matches : {
marginTop: 20
},
item : {
borderRadius: 4,
borderWidth: 0.5,
borderColor: 'green',
marginBottom: 5,
padding: 20,
textAlign: 'center',
},
});
You're probably seeing an issue because the second API request doesn't return an array, it returns an object. The cloneWithRows expects an array. Replacing this line
matchFacts : this.state.matchFacts.cloneWithRows(selectedMatch)
with
matchFacts : this.state.matchFacts.cloneWithRows([selectedMatch])
may help, depending on how you render this new data.
That's just a guess since I don't know what error you're receiving.
I use ReactNative to develop my iOS APP,to realize the QRCode scanner function,i took the react-native-camera component which provide the barcode scanner function to my project.everything goes all right,but when i had succeed in recognizing a QRCode,next time i use the model,the screen just got frozen,seems like the app goes crashed. something interesting that as the screen is frozen,and once the model cancelled from the left button of navigation,The module can work properly.
I'm not sure whether it's a inner bug of NavigatorIOS,or just the bug of react-native-camera itself.
here is the QRCode component code:
'use strict';
var React = require('react-native');
var Dimensions = require('Dimensions');
var {
StyleSheet,
View,
Text,
TouchableOpacity,
VibrationIOS,
Navigator,
} = React;
var Camera = require('react-native-camera');
var { width, height } = Dimensions.get('window');
var QRCodeScreen = React.createClass({
propTypes: {
cancelButtonVisible: React.PropTypes.bool,
cancelButtonTitle: React.PropTypes.string,
onSucess: React.PropTypes.func,
onCancel: React.PropTypes.func,
},
getDefaultProps: function() {
return {
cancelButtonVisible: false,
cancelButtonTitle: 'Cancel',
barCodeFlag: true,
};
},
_onPressCancel: function() {
var $this = this;
requestAnimationFrame(function() {
$this.props.navigator.pop();
if ($this.props.onCancel) {
$this.props.onCancel();
}
});
},
_onBarCodeRead: function(result) {
var $this = this;
if (this.props.barCodeFlag) {
this.props.barCodeFlag = false;
setTimeout(function() {
VibrationIOS.vibrate();
$this.props.navigator.pop();
$this.props.onSucess(result.data);
}, 1000);
}
},
render: function() {
var cancelButton = null;
if (this.props.cancelButtonVisible) {
cancelButton = <CancelButton onPress={this._onPressCancel} title={this.props.cancelButtonTitle} />;
}
return (
<Camera onBarCodeRead={this._onBarCodeRead} style={styles.camera}>
<View style={styles.rectangleContainer}>
<View style={styles.rectangle}/>
</View>
{cancelButton}
</Camera>
);
},
});
var CancelButton = React.createClass({
render: function() {
return (
<View style={styles.cancelButton}>
<TouchableOpacity onPress={this.props.onPress}>
<Text style={styles.cancelButtonText}>{this.props.title}</Text>
</TouchableOpacity>
</View>
);
},
});
var styles = StyleSheet.create({
camera: {
width:width,
height: height,
alignItems: 'center',
justifyContent: 'center',
},
rectangleContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'transparent',
},
rectangle: {
height: 250,
width: 250,
borderWidth: 2,
borderColor: '#00FF00',
backgroundColor: 'transparent',
},
cancelButton: {
flexDirection: 'row',
justifyContent: 'center',
backgroundColor: 'white',
borderRadius: 3,
padding: 15,
width: 100,
marginBottom: 10,
},
cancelButtonText: {
fontSize: 17,
fontWeight: '500',
color: '#0097CE',
},
});
module.exports = QRCodeScreen;
And In another Component I push this qrCode to the new sence:
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
TouchableOpacity,
NavigatorIOS,
AlertIOS,
Navigator,
} = React;
var QRCodeScreen = require('./QRCodeScreen');
var cameraApp = React.createClass({
render: function() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
title: 'Index',
backButtonTitle: 'Back',
component: Index,
}}
/>
);
}
});
var Index = React.createClass({
render: function() {
return (
<View style={styles.contentContainer}>
<TouchableOpacity onPress={this._onPressQRCode}>
<Text>Read QRCode</Text>
</TouchableOpacity>
</View>
);
},
_onPressQRCode: function() {
this.props.navigator.push({
component: QRCodeScreen,
title: 'QRCode',
passProps: {
onSucess: this._onSucess,
},
});
},
// onPressCancel:function(){
//
// this.props.navigator.getContext(this).pop();
//
// },
_onSucess: function(result) {
AlertIOS.alert('Code Context', result, [{text: 'Cancel', onPress: ()=>console.log(result)}]);
// console.log(result);
},
});
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
contentContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}
});
AppRegistry.registerComponent('Example', () => cameraApp);
Any answer will be helpful!
I think it's a inner bug of NavigatorIOS, or maybe just sth else wrong.
Blew is my code, it is ok.
'use strict';
const React = require('react-native');
const {
AppRegistry,
StyleSheet,
Text,
View,
TouchableOpacity,
Navigator,
} = React;
var QRCodeScreen = require('./QRCodeScreen');
const CameraApp = () => {
const renderScene = (router, navigator) => {
switch (router.name) {
case 'Index':
return <Index navigator={navigator}/>;
case 'QRCodeScreen':
return <QRCodeScreen
onSucess={router.onSucess}
cancelButtonVisible={router.cancelButtonVisibl}
navigator={navigator}
/>;
}
}
return (
<Navigator
style={styles.container}
initialRoute={{
name: 'Index',
}}
renderScene={renderScene}
/>
);
};
const Index = ({navigator}) => {
const onPressQRCode = () => {
navigator.push({
name: 'QRCodeScreen',
title: 'QRCode',
onSucess: onSucess,
cancelButtonVisible: true,
});
};
const onSucess = (result) => {
console.log(result);
};
return (
<View style={styles.contentContainer}>
<TouchableOpacity onPress={onPressQRCode}>
<Text>Read QRCode</Text>
</TouchableOpacity>
</View>
);
};
module.exports = CameraApp;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
contentContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}
});
you can try Library react native qrcode
https://github.com/moaazsidat/react-native-qrcode-scanner. on me ,
its run. you can try . in ios and android.
I want to let the red view keep ratio 16:9. I try but failed. I know React Native use Flexbox (Reimplement in Javascript), but I don't know how to do this. Thanks.
Here is my Javascript:
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
View,
} = React;
var AwesomeProject = React.createClass({
render: function() {
return (
<View style={styles.container}>
<View style={styles.banner}>
</View>
<View style={styles.items}>
</View>
</View>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
},
banner: {
backgroundColor: 'red',
flex: 1,
},
items: {
backgroundColor: 'blue',
flex: 3,
},
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
Here is document about Flexbox in React Native:
http://facebook.github.io/react-native/docs/flexbox.html#content
Here is valid style props:
Valid style props: [
"width",
"height",
"top",
"left",
"right",
"bottom",
"margin",
"marginVertical",
"marginHorizontal",
"marginTop",
"marginBottom",
"marginLeft",
"marginRight",
"borderWidth",
"borderTopWidth",
"borderRightWidth",
"borderBottomWidth",
"borderLeftWidth",
"position",
"flexDirection",
"flexWrap",
"justifyContent",
"alignItems",
"alignSelf",
"flex",
"resizeMode",
"backgroundColor",
"borderColor",
"borderRadius",
"tintColor",
"opacity",
"fontFamily",
"fontSize",
"fontWeight",
"fontStyle",
"lineHeight",
"color",
"containerBackgroundColor",
"textAlign",
"writingDirection",
"padding",
"paddingVertical",
"paddingHorizontal",
"paddingTop",
"paddingBottom",
"paddingLeft",
"paddingRight",
"borderTopColor",
"borderRightColor",
"borderBottomColor",
"borderLeftColor",
"overflow",
"shadowColor",
"shadowOffset",
"shadowOpacity",
"shadowRadius",
"transformMatrix",
"rotation",
"scaleX",
"scaleY",
"translateX",
"translateY"
]"
React Native (since 0.40) supports the aspectRatio prop.
You can do:
style={{ aspectRatio: 16/9 }}
See Maintain aspect ratio of image with full width in React Native
You can use on layout function.
class AwesomeProject = extends React.Component<{}> {
constructor(props) {
super(props)
this.state = {width: 0,height:0}
}
onPageLayout = (event) => {
const {width, height} = event.nativeEvent.layout;
this.setState({
width,
height
})
};
render(){
return (
<View style={styles.container}>
<View style={[
styles.banner,
{
height:this.state.width*9/16
}
]}>
</View>
<View style={styles.items}>
</View>
</View>
);
}
});