React native flatlist, not expected behavior - javascript

Hi I am starting to use FlatList component instead of ListView and I am having some problems trying to render the separator, I made a multiple-choice component and it is working ok but I don't understand why it is not rendering the separator from the flatlist, if I put the separator inside the renderItem function it's working ok, but I want to use it from the flatlist as a prop.
One curious thing is if I delete the itemSeparatorComponent prop from the FlatList in render method the component stops updating the checkmark (renderIndicator()) that indicates that item is selected, so it's really annoying this, I put the whole code, please check it.
React native: 0.44.0
import React, { Component } from 'react';
import { Button, Icon, Divider } from 'react-native-elements';
import { FlatList, View, TouchableOpacity, Text } from 'react-native';
import { Card, CardSection } from './commons';
import { appMainColor } from '../constants';
export default class ListOrderItems extends Component {
static navigationOptions = {
title: 'Realice su selección'
};
state = { selected: [], items: this.props.navigation.state.params.items };
onItemPress = (item) => {
const selected = this.state.selected;
const index = selected.indexOf(item.name);
if (index === -1) {
selected.push(item.name);
} else {
selected.splice(index, 1);
}
this.setState({ selected });
};
isSelected = (item) => {
return this.state.selected.indexOf(item.name) !== -1;
};
keyExtractor = (item, index) => {
return index;
};
renderOkButton = () => {
if (this.props.navigation.state.params.type === 'multipleChoice') {
return (
<Button
raised
borderRadius={5}
backgroundColor={appMainColor}
title='Aceptar'
onPress={() => this.props.navigation.goBack()}
/>
);
}
};
renderCancelButton = () => {
return (
<Button
raised
borderRadius={5}
backgroundColor={appMainColor}
title='Cancelar'
onPress={() => this.props.navigation.goBack()}
/>
);
};
renderIndicator = (item) => {
if (this.isSelected(item)) {
return <Icon name="check-circle" color={appMainColor} />;
}
};
renderSeparator = () => {
return <Divider />;
};
renderItem = ({ item, index }) => {
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => this.onItemPress(item, index)}
>
<View style={styles.row}>
<View style={styles.optionLabel}>
<Text>{item.name} (${item.price})</Text>
</View>
<View style={styles.optionIndicator}>
{this.renderIndicator(item, index)}
</View>
</View>
</TouchableOpacity>
);
};
render() {
return (
<View>
<Card>
<CardSection>
<FlatList
data={this.state.items}
keyExtractor={this.keyExtractor}
renderItem={this.renderItem}
itemSeparatorComponent={() => this.renderSeparator()}
/>
</CardSection>
</Card>
<Card>
<CardSection style={{ justifyContent: 'space-around' }}>
{this.renderOkButton()}
{this.renderCancelButton()}
</CardSection>
</Card>
</View>
);
}
}
const styles = {
row: {
flexDirection: 'row',
padding: 5
},
optionLabel: {
flex: 1,
},
optionIndicator: {
width: 30,
height: 30,
justifyContent: 'center',
alignItems: 'center'
}
};

I think you made some typo, it should be ItemSeparatorComponent, not itemSeparatorComponent.

Flatlist list items are pure components if you want to check with are selected you should set this in the data source you pass in. Otherwise, the props for the item remain the same and the component will not rerender.
For the divider can you try itemSeparatorComponent={Divider}

Related

Not sure how to remove this FlatList from displaying by default on screen

Currently, this - is how the SearchBar and FlatList is showing up on the screen. Upon clicking on the SearchBar and typing one of the list components, it shows up this- way. I want the FlatList to only appear when I click on the SearchBar. How do I implement this in the app? Something like a dropdown search bar...
import React from 'react';
import { View, Text, FlatList, ActivityIndicator, StyleSheet } from 'react-native';
import { SearchBar } from 'react-native-elements';
import { Button, Menu, Divider, Provider, TextInput } from 'react-native-paper';
const restaurantList = [
{
type: 'Italian',
name: 'DiMaggio'
},
{
type: 'Greek',
name: 'Athena'
}
];
export default class App extends React.Component {
static navigationOptions = {
title: 'Search for Restaurants'
};
constructor (props) {
super(props);
this.state = {
loading: false,
data: restaurantList,
error: null,
value: '',
};
this.arrayholder = [];
}
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: '86%',
backgroundColor: '#CED0CE',
marginLeft: '14%'
}}
/>
);
};
searchFilterFunction = text => {
this.setState({
value: text
});
const newData = restaurantList.filter(item => {
console.log(item.type)
const itemData = `${item.name.toUpperCase()} ${item.type.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.includes(textData);
});
this.setState({
data: newData
});
};
renderHeader = () => {
return (
<SearchBar
placeholder="Type..."
value={this.state.value}
onChangeText={text => this.searchFilterFunction(text)}
/>
);
};
render () {
if (this.state.loading) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator />
</View>
);
} else {
return (
<Provider>
<View
style={{
paddingTop: 50,
flexDirection: 'row',
justifyContent: 'center',
}}>
<View style={styles.container}>
<FlatList
keyExtractor={(item, index) => `${index}`}
extraData={this.state}
data={this.state.data}
renderItem={({ item }) => (
<Text>{item.name} {item.type}</Text>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
/>
</View>
</View>
<View style={styles.container}>
<TextInput />
</View>
</Provider>
);
}
}
}
Try this way
this.state = {
searchClicked: false
};
<SearchBar
placeholder="Type..."
value={this.state.value}
onChangeText={text => this.searchFilterFunction(text)}
onFocus={() => this.setState({searchClicked: true})}
onBlur={() => this.setState({searchClicked: false})}
/>
<View>{this.renderHeader()}</View>
{this.state.searchClicked && <FlatList
keyExtractor={(item, index) => `${index}`}
extraData={this.state}
data={this.state.data}
renderItem={({ item }) => (
<Text>{item.name} {item.type}</Text>
)}
ItemSeparatorComponent={this.renderSeparator}
// ListHeaderComponent={this.renderHeader} <—- Remove from here —->
/>}
In case #Lakhani reply doesn't satisfy your question, I have another suggestion for you.
Your SearchBar should contain a TextInput. You can use onFocus and onBlur props to capture event you click on SearchBar or leave it.
...
<SearchBar
placeholder="Type..."
value={this.state.value}
onChangeText={text => this.searchFilterFunction(text)}
onFocus={()=>this.setState({showList: true})} // <---
onBlur={()=>this.setState({showList: false})} // <---
/>
...
render() {
return (
...
{
this.state.showList && <FlatList ... />
}
)
}

How to use react-native-action-sheet with hooks

I'm trying to use https://github.com/expo/react-native-action-sheet in a functional component using the provided hook useActionSheet(). I'm already using the class components version without any problem but I'd like to switch to functional.
React version is 16.9.0
This is my component
import {
connectActionSheet,
useActionSheet,
} from "#expo/react-native-action-sheet";
import React, { Component } from "react";
import { View, Text, SafeAreaView } from "react-native";
import TaButton from "../components/TaButton";
import { typography, styles, buttons } from "../components/Styles";
const UploadUI: React.FC<{
description: string;
label: string;
}> = (props) => {
const { showActionSheetWithOptions } = useActionSheet();
const openActionSheet = () => {
console.log("TEST - Choosing action now");
const options = [
"Scegli dalla libreria",
"Scatta una foto",
"Carica un file",
"Annulla",
];
//const destructiveButtonIndex = 0;
const cancelButtonIndex = options.length - 1;
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
//destructiveButtonIndex,
},
(buttonIndex) => {
// Do something here depending on the button index selected
switch (buttonIndex) {
case 0:
console.log('Case 0')
return;
case 1:
console.log("Case 1");
return;
case 2:
console.log("Case 2");
return;
default:
}
}
);
};
const { description, label } = props;
return (
<View style={{ flexDirection: "row", height: 50, marginBottom: 30 }}>
<View style={{ flex: 0.7, justifyContent: "center" }}>
<Text style={typography.body}>{description}</Text>
</View>
<View style={{ flex: 0.3 }}>
<TaButton
style={buttons.primary}
labelStyle={buttons.primaryLabel}
onPress={() => openActionSheet()}
label={label}
/>
</View>
</View>
);
};
const URWStep4: React.FC = (props) => {
return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
<View style={{ paddingVertical: 30, marginBottom: 20 }}>
<Text style={typography.title}>
Ci serviranno alcuni documenti per verificare la tua identità
</Text>
</View>
<UploadUI
description="Carta d'identità - Fronte"
label="Carica"
></UploadUI>
<UploadUI
description="Carta d'identità - Retro"
label="Carica"
></UploadUI>
</View>
</SafeAreaView>
);
};
export default connectActionSheet(URWStep4);
When clicking on buttons, the sentence "TEST - Choosing action now" is logged ad expected, but nothing more happens. The actionsheet does not open.
Check you have wrapped your top-level component with <ActionSheetProvider /> even when using hooks
https://github.com/expo/react-native-action-sheet#1-wrap-your-top-level-component-with-actionsheetprovider-
using useRef, I saw the issue in lib and Allanmaral's answer:
import ActionSheet from 'react-native-actionsheet'
const Demo = (props) => {
const refActionSheet = useRef(null);
showActionSheet = () => {
if (refActionSheet.current) {
refActionSheet.current.show();
}
}
return (
<View>
<Text onPress={this.showActionSheet}>Open ActionSheet</Text>
<ActionSheet
ref={refActionSheet}
title={'Which one do you like ?'}
options={['Apple', 'Banana', 'cancel']}
cancelButtonIndex={2}
destructiveButtonIndex={1}
onPress={(index) => { /* do something */ }}
/>
</View>
)
}

typeError: undefined is not an object (evaluating 'item.phoneNumbers[0]')

I want to render my contact list in my app using expo-contacts, the list display for about 2 seconds, then i get typeError: undefined is not an object (evaluating 'item.phoneNumbers[0]'). I have checked the documentation to see if I made any errors, but i could not find any. Does anyone have a work around this
below is my code
ContactList.js
import React, { Component } from "react";
import {
View,
Text,
Platform,
StatusBar,
FlatList,
StyleSheet,
ActivityIndicator
} from "react-native";
import * as Contacts from "expo-contacts";
import * as Permissions from "expo-permissions";
class ContactList extends Component {
static navigationOptions = {
header: null
};
constructor(props) {
super(props);
this.state = {
isLoading: false,
contacts: []
};
}
async componentDidMount() {
this.setState({
isLoading: true
});
this.loadContacts();
}
loadContacts = async () => {
const permissions = await Permissions.askAsync(Permissions.CONTACTS);
if (permissions.status !== "granted") {
return;
}
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers, Contacts.Fields.Emails]
});
this.setState({
contacts: data,
isLoading: false
});
};
handleBack() {
this.props.navigation.goBack();
}
renderItem = ({ item }) => (
<View style={{ minHeight: 70, padding: 5 }}>
<Text>
{item.firstName}
{item.lastName}
</Text>
<Text>{item.phoneNumbers[0].digits}</Text>
</View>
);
render() {
const { isLoading, contacts } = this.state;
let emptyContact = null;
emptyContact = (
<View style={styles.emptyContactStyle}>
<Text style={{ color: "red" }}>No Contacts Found</Text>
</View>
);
return (
<SafeAreaView style={styles.contentWrapper}>
<View style={styles.contentWrapper}>
{isLoading ? (
<View style={styles.isLoadingStyle}>
<ActivityIndicator size="large" color="#2484E8" />
</View>
) : null}
<FlatList
data={contacts}
renderItem={this.renderItem}
keyExtractor={(item, index) => index.toString()}
ListEmptyComponent={emptyContact}
/>
</View>
</SafeAreaView>
);
}
}
Here is a new answer because the previous one was off topic. The error occurs because the displayed contact doesn't have a phoneNumber.
You should check first that a phone number exists before displaying it:
renderItem = ({ item }) => (
<View style={{ minHeight: 70, padding: 5 }}>
<Text>
{item.firstName}
{item.lastName}
</Text>
<Text>
{item.phoneNumbers && item.phoneNumbers[0] && item.phoneNumbers[0].digits}
</Text>
</View>
);

Create rows in React Native

i want to create rows for images,which recieve from _find function.This function already seperated array to subarrays,which number equals number of rows,how i can render rows with data from _find?Dont purpose ready solutions such as react-native-easy-grid,i want to do it without another libs,and can i scroll items if i use this way?
import React, { Component } from 'react';
import { AppRegistry, Text, TextInput, View,StyleSheet,Button,Image,ScrollView,Dimensions,ListView } from 'react-native';
import Grid from './GridBuilder.js';
const regUrl = /(src=")?(https:\/\/\S{2,500})(")/gm;
var IMAGES_PER_ROW = 3;
let app1;
export default class inputText extends Component {
constructor(props) {
super(props);
app1 = this;
this.state = {
text: null,
findEvent:false,
im_p_r:3,
items:{},
};
}
render() {
return (
<View style={{margin: 20}}>
<TextInput
style = {styles.searchInput}
placeholder="Type here to search"
onChangeText={(text) => this.setState({text})}
/>
<Button
onPress={() => this._find(this.state.text)}s
title='Find'
color="#841584"
accessibilityLabel="on"
/>
{this.state.findEvent && <DisplayImage />}
</View>
);
}
_find(searchText){
fetch('https://www.googleapis.com/customsearch/v1?key=AIzaSyAfcN3jfimFxHxpHNjhHOSuuY8dm5YZnqQ&cx=007223195539364418542:lcqjo0djp7k&num=10&q='+ searchText+'&searchType=image')
.then((resp) => resp.json())
.then(function(data) {
let s = data.items;
let SIZE = IMAGES_PER_ROW;
let res = s.reduce((p,c)=>{
if(p[p.length-1].length == SIZE){
p.link.push([]);
}
p[p.length-1].push(c);
return p.link;
}, [[]])
app1.setState({items:res,findEvent:true});
})
}
}
export class DisplayImage extends Component {
render(){
return(
<View style={styles.container}>
{app1.state.items.map((item,index) => <View style={styles.row} ><Image style={[styles.image,styles.box]} source={{uri:item.link}} key={index} /></View>)}
</View>
)
}
}
const styles = StyleSheet.create({
searchInput:{
fontSize:20,
paddingTop:20,
paddingBottom:20
},
image:{
paddingTop:20,
width:100,
height:100,
},
row: {
flex: 1,
flexWrap: 'wrap',
flexDirection: 'row',
justifyContent: 'space-between'
},
box: {
flex: 1,
height: 100,
width:100,
backgroundColor: '#333',
},
})
AppRegistry.registerComponent('inputText', () => inputText);
AppRegistry.registerComponent('DisplayImage', () => DisplayImage);
You can use FlatList from React Native.
{this.state.findEvent && <FlatList
data={this.state.items}
renderItem={({ item }) => this.renderItem(item)}
/>}
FlatList receive as data the list of elements you want to render, in this case the data returned from the find function.
And then define the renderItem function like:
renderItem(item) {
return (
<View style={styles.row} >
<Image
style={[styles.image,styles.box]}
source={{uri:item.link}} key={index}
/>
</View>
);
}
This function is in charge of rendering the list of images, each image as a row as you want.
FlatList is really useful and makes lists rendering easier. You get the scroll by default and you can also render separators, have a pull to refresh, etc. Take a look to the FlatList doc to see all properties available.
Here is working example of Flat list by which you can get images in the row
https://snack.expo.io/SJDoljDg7
FlatList is the way to go but I suspect the spec has changed since the original accepted answer. You must now provide a key extractor, here is an example of what worked for me:
const listItems = [
{
"id": 0.7967679550647925,
"name": "Hcsrhjkh",
},
{
"id": 0.3212834674770011,
"name": "Phvdgbb",
},
{
"id": 0.30092504022778455,
"name": "Hnvdghbh",
},
]
...
{listItems.length < 1 ? (
<Text style={{ fontSize: 30 }}>Your list is empty.</Text>
) : (
<FlatList
data={listItems}
renderItem={({item}) => <ListItem item={item} />}
keyExtractor={(item) => item.id.toString()}
/>
)}
As you can might have found, the keyExtractor expects a string so I've coerced the 'id' which is a number to a string.

Programmatically add a component in React Native

Suppose I have a simple React Native app like so:
'use strict';
var React = require('react-native');
var {
AppRegistry,
Text,
TouchableHighlight,
View,
} = React;
var ReactProject = React.createClass({
_onPressOut: function() {
// What do we do here?
},
render() {
return (
<View>
<Text>This text should be before</Text>
<Text>This text should be after</Text>
<TouchableHighlight onPressOut={this._onPressOut}>
<Text>Tap Me</Text>
</TouchableHighlight>
</View>
);
}
});
AppRegistry.registerComponent('ReactProject', () => ReactProject);
How can I dynamically insert a component between the first and second Text tags when the TouchableHighlight is pressed?
Try creating an array and attaching it to the state. You can then push items to the array, and reset the state.
https://rnplay.org/apps/ymjNxQ
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
TouchableHighlight
} = React;
var index = 0
var SampleApp = React.createClass({
getInitialState(){
return { myArr: [] }
},
_onPressOut() {
let temp = index ++
this.state.myArr.push(temp)
this.setState({
myArr: this.state.myArr
})
},
render() {
let Arr = this.state.myArr.map((a, i) => {
return <View key={i} style={{ height:40, borderBottomWidth:2, borderBottomColor: '#ededed' }}><Text>{ a }</Text></View>
})
return (
<View style={styles.container}>
<Text>First</Text>
{ Arr }
<Text>Second</Text>
<TouchableHighlight style={ styles.button } onPress={ () => this._onPressOut() }>
<Text>Push</Text>
</TouchableHighlight>
</View>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
marginTop:60
},
button: {
height:60,
backgroundColor: '#ededed',
marginTop:10,
justifyContent: 'center',
alignItems: 'center'
}
});
AppRegistry.registerComponent('SampleApp', () => SampleApp);
I've set up a working example here.
In react or react native the way component hide/show or add/remove does not work like in android or iOS. Most of us think there would be the similar stratedgy like
View.hide = true or parentView.addSubView(childView
But the way react native work is completely different. The only way to acheive this kind of functionality is to include your component in your DOM or remove from DOM.
Here in this example I am going set the visibility of text view based on the button click.
enter image description here
The idea behind this task is the create a state variable called state having the initial value set to false when the button click event happens then it value toggles. Now we will use this state variable during the creation of component.
import renderIf from './renderIf'
class fetchsample extends Component {
constructor(){
super();
this.state ={
status:false
}
}
toggleStatus(){
this.setState({
status:!this.state.status
});
console.log('toggle button handler: '+ this.state.status);
}
render() {
return (
<View style={styles.container}>
{renderIf(this.state.status)(
<Text style={styles.welcome}>
I am dynamic text View
</Text>
)}
<TouchableHighlight onPress={()=>this.toggleStatus()}>
<Text> touchme </Text>
</TouchableHighlight>
</View>
);
}
}
the only one thing to notice in this snippet is renderIf which is actually a function which will return the component passed to it based on the boolean value passed to it.
renderIf(predicate)(element).
renderif.js
'use strict';
const isFunction = input => typeof input === 'function';
export default predicate => elemOrThunk =>
predicate ? (isFunction(elemOrThunk) ? elemOrThunk() : elemOrThunk) : null;
With React components you don't want to think of actions reaching into the DOM and inserting components - you want to think components responding to actions. Theoretically, this component is already composed and ready, it just needs to know if it should be rendered or not:
var ReactProject = React.createClass({
getInitialState() {
// our *state* dictates what the component renders
return {
show: false
};
}
_onPressOut: function() {
// update our state to indicate our "maybe" element show be shown
this.setState({show: !this.state.show});
},
maybeRenderElement() {
if (this.state.show) {
// depending on our state, our conditional component may be part of the tree
return (
<Text>Yay!</Text>
);
}
return null;
}
render() {
return (
<View>
<Text>This text should be before</Text>
{this.maybeRenderElement()}
<Text>This text should be after</Text>
<TouchableHighlight onPressOut={this._onPressOut}>
<Text>Tap Me</Text>
</TouchableHighlight>
</View>
);
}
});
I've also made a helper that makes it easy to conditionally render things, render-if
renderIf(this.state.show)(
<Text>Yay</Text>
)
ECMA6 Syntax
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
TouchableHighlight
} from 'react-native';
export default class fourD extends Component {
constructor(props) {
super(props);
let ele1 = (
<View key={1}>
<Text>Element {1}</Text>
<TouchableOpacity onPress={ () => this._add() }>
<Text>Add</Text>
</TouchableOpacity>
</View>
);
this.state = {
ele: [],
key: 1
}
this.state.ele.push(ele1);
}
_add(){
let key = this.state.key + 1;
let ele2 = (
<View key={key}>
<Text>Element {key}</Text>
<TouchableOpacity onPress={ () => this._add() }>
<Text>Add</Text>
</TouchableOpacity>
</View>
);
let ele = this.state.ele;
ele.push(ele2);
this.setState({ ele: ele,key : key})
}
render() {
return (
<View style={styles.container}>
<Text>This text should be before</Text>
{ this.state.ele }
<Text>This text should be after</Text>
<TouchableHighlight onPressOut={ () => this._add() }>
<Text>Tap Me</Text>
</TouchableHighlight>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "white",
}
})

Categories