React-Native FlatList not re-rendering with custom renderItem - javascript

I have a FlatList that works as expected when using a plain old <Text> tag, but when using a custom Component inside renderItem, the FlatList will not re-render when changing this.state.dayOfYear. On app load, when I set this.state.dayOfYear, it loads properly. But when I change state again, it will not change the FlatList.
FlatList Code
<FlatList
style={{flex: 1}}
extraData={this.state}
data={reading_data[this.state.dayOfYear]}
renderItem={({item}) => <DayRow content={item}/>} //does not work
// renderItem={({item}) => <Text>{item.ref}</Text>} //Works perfectly
keyExtractor={(item, index) => index}
/>
Custom renderItem (DayView.js)
import {StyleSheet, Text, View} from 'react-native'
import React, {Component} from 'react';
export default class DayRow extends React.Component {
constructor(props) {
super(props)
console.log(props)
this.state = {
content: props.content,
}
}
render() {
return (
<View style={styles.row}>
<Text style={styles.text}>{this.state.content.ref}</Text>
<View style={{height: 2, backgroundColor:'#abb0ab'}}/>
</View>
);
}
}
const styles = StyleSheet.create({
row: {
backgroundColor: '#fff'
},
text: {
fontSize: 16,
padding: 10,
fontWeight: 'bold',
color: '#000',
},
});
module.exports = DayRow;

I'm pretty sure that your DayRow items are being constructed before props.content is being set, you need to grab the props when the component is mounting. Try adding this:
componentWillMount() {
const { content } = this.props;
this.setState({content: content});
}
EDIT
I missed the part about "re-rendering"...
Basically you need a block of code that updates your components state when its props change, react components have another function similar to componentWillMount called componentWillReceiveProps, try:
componentWillReceiveProps(nextProps) {
const { content } = nextProps;
this.setState({content: content});
}

I had the same issue but resolved using extraData = {this.state}
Complete code is here
<FlatList
style={styles.listView}
data={this.state.readingArray}
extraData={this.state}
renderItem=
{({item})=>

Related

Why is the data from Firebase not rendering in my FlatList (React Native)?

I'm a beginner in React Native and I'm trying to display my data from Google Firebase in a FlatList. There are no errors that pop up, but nothing is shown in my list. I know that the componentDidMount() section works, as when I add a console.log inside it shows that offers has the correct objects inside. I'm not sure why it doesn't show up on the screen though...
import React, {Component} from 'react';
import {View, Text, StyleSheet, TextInput, SafeAreaView, Platform, Image, FlatList, TouchableHighlight} from "react-native";
import { List, ListItem, Divider } from 'react-native-elements';
import Icon from "react-native-vector-icons/Ionicons";
import { db } from '../config';
let offersRef = db.ref('/offers');
class Home extends Component {
state = {
offers: [],
currentUser: null
};
componentDidMount() {
let mounted = true;
if(mounted){
offersRef.on('value', snapshot => {
let data = snapshot.val();
let offers = Object.values(data);
this.setState({ offers });
});
}
return () => mounted = false;
}
pressRow()
{
console.log(item)
}
renderItem(item){
return(
<TouchableHighlight onPress={() => {this.pressRow(item)}}>
<Text>
{item.name}
</Text>
</TouchableHighlight>
)
}
render() {
const { currentUser } = this.state
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList>
data = {this.state.offers}
renderItem = {({ data }) => (
<TouchableHighlight onPress={() => {this.pressRow(data)}}>
<Text>
{data.name}
</Text>
</TouchableHighlight>
) }
</FlatList>
</SafeAreaView>
);
}
}
export default Home;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
My code is above, it would be very much appreciated if someone is able to help! Thank you...
Hey please do the following changes in your render method:-
Replace "data" in renderItem with "item" and with in renderItem where ever accordingly.
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data = {this.state.offers}
renderItem = {({item} ) => (
<TouchableHighlight onPress={() => {this.pressRow(item)}}>
<Text>
{item.name}
</Text>
</TouchableHighlight>
) }
/>
</SafeAreaView>

How to add input fields into a list in React Native?

I'm a beginner in React Native ans struggling with adding Input (Search bars) into a list by clicking a button. Here's my code:
import React, { useState } from "react";
import {
View,
Text,
Button,
FlatList
} from 'react-native'
import InputDemo from '../components/InputDemo'
const INCREMENT = 1;
class AddInputDemo extends React.Component{
constructor(props){
super(props);
this.state={
counter: 0,
numOfInput: [0]
}
this.addInput = this.addInput.bind(this)
}
addInput(){
this.setState((state) => ({
counter: state.counter + INCREMENT,
numOfInput: [...state.numOfInput, state.counter]
}))
console.log(this.state.counter);
console.log(this.state.numOfInput);
}
render(){
return(
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<FlatList
data={this.state.numOfInput}
keyExtractor={(item, index) => item.id}
renderItem={itemData => {
<InputDemo/>
}}
/>
<Button title='Add a location' onPress={this.addInput} />
</View>
);
}
}
export default AddInputDemo;
Here's the InputDemo file:
import * as React from 'react'
import {
View,
Text,
TextInput,
Button
} from 'react-native'
const InputDemo = props => {
return(
<View style={{borderColor: 'black', borderWidth: 1}}>
<TextInput
placeholder='Search'
/>
</View>
)
}
export default InputDemo;
It's weird since I use this same logic with state in Functional Component. It works. But when applying to a Class Component, it does not show anything when I click that button.
THANKS FOR ANY HELP !!!
EDIT
I tried to use extraData:
<FlatList
extraData={this.state.numOfInput}
keyExtractor={(item, index) => item.id}
renderItem={itemData => {
<InputDemo
id={itemData.item.id}
/>
}}
/>
And created an id for each InputDemo:
const InputDemo = props => {
return(
<View key={props.id} style={{borderColor: 'black', borderWidth: 1}}>
<TextInput
placeholder='Search'
/>
</View>
)
}
But it still does not work
Please help !!!
FlatList data attribute takes prop as Array. Documentation is your bestfriend.
Everything goes more or less like below, not tested but closer to what you want, I hope.
import React, { useState } from "react";
import {
View,
Text,
Button,
FlatList
} from 'react-native'
import InputDemo from '../components/InputDemo'
const INCREMENT = 1;
class AddInputDemo extends React.Component{
constructor(props){
super(props);
this.state={
counter: 0,
numOfInput: [0],
item:'',
searchArray:[],
}
this.addInput = this.addInput.bind(this)
}
addInput(){
this.setState((state) => ({
counter: state.counter +=1,
searchArray:[...this.state.searchArray, this.state.item] //appends search item to search array
numOfInput: [...state.numOfInput, state.counter] //don't know why you need this
}))
}
render(){
return(
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<InputDemo search={(searchItem)=>this.setState({item:searchItem})}/>
<FlatList
data={this.state.searchArray}
keyExtractor={(item, index) => item.id}
renderItem={itemData => {
<Text>{itemData}</Text>
}}
/>
<Button title='Add a location' onPress={this.addInput} />
</View>
);
}
}
export default AddInputDemo;
And Input Demo
import * as React from 'react'
import {
View,
TextInput
} from 'react-native'
const InputDemo = props => {
const onChangeText = (item) => {
props.search(item); //add search item to state of "class AddInputDemo" using props
}
return(
<View style={{borderColor: 'black', borderWidth: 1}}>
<TextInput
placeholder='Search'
onChangeText={text => onChangeText(text)}
/>
</View>
)
}
export default InputDemo;
EDIT 2
Hi guys, I know where the error is now. It's not about the data or extraData. The solution is we have to wrap around the <InputDemo/> with a return statement. It works well then. Thank you all for the helpful answers.
You should pass extraData
A marker property for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.
<FlatList
data={this.state.numOfInput}
extraData={counter}
keyExtractor={(item, index) => item.id}
renderItem={itemData => (
<InputDemo/>
)}
/>
Edit:
You also have a huge problem, your data don't have .id prop and keyExtractor probably isn't working.
You could change it to
keyExtractor={(item, index) => index.toString()}
But this still isn't good, try adding unique id prop to each item.

How to call function from different component bound to flatlist in React Native

I am creating a chat application in React Native that receives different "message types" as a response. One of them contains an array of buttons that I am currently rendering in a Flatlist in Component "ChatQuickReply" that looks like this:
import React from "react";
import {
StyleSheet,
FlatList,
View,
TouchableOpacity,
Text
} from "react-native";
class ChatQuickReply extends React.Component {
constructor(props) {
super(props);
}
renderItem({ item }) {
return (
<TouchableOpacity onPress={this._onPressQuickReply}>
<View style={styles.quickButton}>
<Text style={styles.quickButtonText}>{item.title}</Text>
</View>
</TouchableOpacity>
);
}
_onPressQuickReply = () => {
alert(Hello);
};
render() {
return (
<View>
<FlatList
data={this.props.buttons}
keyExtractor={(item, index) => "key" + index}
renderItem={this.renderItem}
/>
</View>
);
}
}
I am rendering this component in a different component also in a Flatlist which works fine.
The problem is, that I am not able to call the function that I am assigning to my TouchableOpacity. How can I call this function from a different component?
I think, what you can try to trigger onPress event for your TouchableOpacity component.
You need ref for this.
ref={touchableOpacity => this._touchableOpacity = touchableOpacity}
then when you want to launch onPress without clicking it just call
this._touchableOpacity.touchableHandlePress();
Depending on the relationship between both components, if the ChatQuickReply reply is a parent component, you can pass the function in the child as props and call it.
import React from "react";
class ChatQuickReply extends React.Component {
renderItem({ item }) {
return (
<TouchableOpacity onPress={this._onPressQuickReply}>
<View style={styles.quickButton}>
<Text style={styles.quickButtonText}>{item.title}</Text>
</View>
</TouchableOpacity>
);
}
_onPressQuickReply = () => {
alert(Hello);
};
render() {
return (
<View>
<FlatList
data={this.props.buttons}
keyExtractor={(item, index) => "key" + index}
renderItem={this.renderItem}
/>
**<ChildComponent clickParentFunction={this._onPressQuickReply} />**
</View>
);
}
}
class ChildComponent extends React.Component {
onClick = () => {
const {clickParentFunction} = this.props
clickParentFunction() // We can call it here
}
// We create a trigger to the onclick
}
or you can take the _onPressQuicklyReply function to the component that renders both components and pass it in to make it more generic
import OtherComponent from './Othercomponent';
class ParentComponent {
_onPressQuickReply = () => {
alert(Hello);
}
return (
<View>
<ChatQuicklyReply onParentFunction={this._onPressQuickly}>
<OtherComponent onParentFunctionCall={this._onPressQuickly} />
</View>
)
}

How can I remove an item from a FlatList and then update that list in React Native?

I am making a To Do list app using React Native, where I add events to a FlatList and then have a button that removes that event once it it finished. So far this is what I have. It seems very hacky to me, but most of it works.
import React from 'react';
import { StyleSheet, Text, View, TextInput,TouchableOpacity, FlatList} from 'react-native';
export default class App extends React.Component {
constructor(props){
const data = [];
super(props);
this.state ={
text: 'Enter activity here',
data: data,
color: true,
currNum: 0,
}
}
updateText(){
this.setState({data:this.state.data.concat({key:this.state.text,index:this.state.currNum})});
this.state.currNum++;
}
removeText(item){
this.setState({data:this.state.data.pop(item.index)});
this.state.currNum--;
}
render() {
return (
<View style={styles.container}>
<Text></Text>
<View style = {{flexDirection:'row',justifyContent:'flex-end'}}>
<TextInput style = {{fontSize:30,borderColor:'black', flex:1, marginTop:20}} onChangeText = {(text) => this.setState({text})}value = {this.state.text}/>
<TouchableOpacity style = {{marginTop:20}}onPress = {()=>(this.updateText())}>
<Text>Add to list</Text>
</TouchableOpacity>
</View>
<View style = {{flex:1, flexDirection:'row'}}>
<FlatList
data = {this.state.data}
extraData = {this.state}
renderItem = {({item}) => <View><Text style={styles.text} >{item.key}</Text><TouchableOpacity onPress = {() => this.removeText(item)}><Text>Remove</Text></TouchableOpacity></View>}
/>
</View>
</View>
);
}
}
When I press the "remove" button, I delete an element from the list of data that the FlatList uses. However, whenever I do this I get an error saying "Tried to get frame for out of range index NaN". Is there a way for me to regularly update and remove a FlatList, and to re-render the FlatList once I have removed an item? I have tried using the extraDate prop, but it hasn't worked. I believe I am using it wrong though. Thank you for all the help.
I think you should use .filter
removeText(item){
this.setState({
data: this.state.data.filter((_item)=>_item.key !== item.key)
});
}
And what this.state.currNum - is for?
use this instead you shouldn't mutate this.state and Array.prototype.pop() mutates it
removeText(item, index){
this.setState({data: [
...this.state.data.slice(0, index),
...this.state.data.slice(index + 1)
]});
this.state.currNum--;
}

Conditional Rendering of child elements in React

I am trying to write a reusable Header Component in React-Native. I want to write it in a ways that the left and right button can be passed as child components. To know where to render which button I want to pass a prop like rightIcon or leftIcon. However I don't know how to access these props.
This is my App.js file
import React from 'react';
import {StyleSheet, TouchableHighlight, View} from 'react-native';
import Header from "./src/Header";
import {Ionicons} from '#expo/vector-icons';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Header headerText={"Barcode Scanner"}>
<TouchableHighlight righticon>
<Ionicons name="md-barcode" size={36} color="white"></Ionicons>
</TouchableHighlight>
</Header>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
});
And this is the Header Component
import React from 'react';
import {Text, View} from 'react-native';
export default class Header extends React.Component {
render() {
const {textStyle, viewStyle, rightButton} = styles;
return (
<View style={viewStyle}>
<Text style={textStyle}>{this.props.headerText}</Text>
<View style={rightButton}>
{this.renderRightChild()}
</View>
</View>
);
}
renderRightChild = () => {
console.log("Check if rightIcon Prop is set");
}
}
const styles = {
viewStyle: {
backgroundColor: '#5161b8',
justifyContent: 'center',
alignItems: 'center',
height: 80,
paddingTop: 25,
shadowColor: '#000',
shadowOffset: {width: 0, height: 2},
shadowOpacity: 0.2,
elevation: 2,
position: 'relative'
},
textStyle: {
color: '#fff',
fontSize: 20
},
rightButton: {
position: 'absolute',
top:
35,
right:
20
}
}
;
I already tried to use React.Children.toArray but this always throws an error that the request entity is too large.
Thanks for all the answers
I guess you can always use a render prop that way you can not only decide whether to render left/right icon component but the component rendering the icon does not even have to know what to render:
The term “render prop” refers to a simple technique for sharing code
between React components using a prop whose value is a function.
return (
<View style={styles.container}>
<Header
headerText={"Barcode Scanner"}
renderRightIcon={() => (
<TouchableHighlight righticon>
<Ionicons name="md-barcode" size={36} color="white" />
</TouchableHighlight>
)}
/>
</View>
);
Then you can use call the right icon as a function:
return (
<View style={viewStyle}>
<Text style={textStyle}>{this.props.headerText}</Text>
{renderLeftIcon && (
<View style={leftButton}>
{renderLeftIcon()}
</View>)
}
{renderRightIcon && (
<View style={rightButton}>
{renderRightIcon()}
</View>)
}
</View>
);
You render both components, the right and left and you put an if condition inside state.
Header Component render method
render() {
const { leftOrRight } = this.props // right - true, left - false
return(
...
{ leftOrRight ? <RightIcon /> : <LeftIcon />}
);
}
Inside Component that calls Header
import Header from './somepath ...';
class Something extends React.Component {
this.state = { leftOrRight }
render() {
return(
<Header leftOrRight = {this.state.LeftOrRight}/>
);
}
}
You could have a function that sets leftOrRight in your parent class
One way to do this is write a Header Component and pass all the things, as props, which you can then access them in Header Components Props like..
<Header title="HeaderTitle"
leftButtonTitle="LeftButton"
rightButton={canBeAObjectWithSomeInfo}
leftButtonClick={handleClick} />
and then in your header component(can be class or a function)
const Header = ({}) => (
<View>
<View onPress={this.props.handleClick}>{this.props.leftButton}</View>
<View>{this.props.title}</View>
<View onPress={this.props.handleRightClick}>{this.props.rightButton}</View>
</View>
)
something like this you can have and then you can design header accordingly

Categories

Resources