How to force Flatlist to re-render after getting a single data? - javascript

I've faced an issue with flatlist when I get single data from the server and set these into state and passes into data props, I can't see any update in the render "I'm adding some Loading if I'd not received any data I show an let's say Indicator" so the indicator disappears and I see blank screen!!
FYI, When I enable Hot Reloading and just press Save in my IDE I can see the single Data in my Screen!
So how can I force it to appear the data!
Code
import React, { Component } from "react";
import firebase from "react-native-firebase";
import Icon from "react-native-vector-icons/Ionicons";
import _ from "lodash";
import {
View,
Text,
StyleSheet,
FlatList,
TouchableOpacity,
Image,
Dimensions
} from "react-native";
const { width } = Dimensions.get("screen");
class ListChats extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
noChat: true
};
}
_getUsers = () => {
let currentUser = firebase.auth().currentUser.uid;
let ref = firebase.database().ref(`Messages/${currentUser}`);
let usersKeys = [];
ref.once("value").then(snapshot => {
snapshot.forEach(childsnap => {
usersKeys.push(childsnap.key);
});
let usernames = [];
usersKeys.forEach(key => {
firebase
.database()
.ref("users")
.child(key)
.once("value")
.then(usersShot => {
let username = usersShot.val().username;
usernames.push({ username: username, key: key });
});
});
this.setState({ users: usernames,noChat: false });
});
};
componentDidMount() {
this._getUsers();
}
render() {
if (this.state.noChat) {
console.log("IF", this.state.users);
return (
<View style={styles.container}>
<Image
style={{
width,
height: width * 0.7,
resizeMode: "contain"
}}
source={require("../../assets/empty.gif")}
/>
<Text style={{ alignSelf: "center" }}>No Chats Found</Text>
</View>
);
} else {
console.log("Else", this.state.users);
return (
<View style={styles.container}>
<FlatList
key={Math.random() * 1000}
extraData={this.state} // I'm already added
data={this.state.users}
contentContainerStyle={{ flexGrow: 1 }}
renderItem={({ item }) => {
return (
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate("ChatDetails", {
Key: item.key,
userName: item.username
})
}
>
<View style={styles.parent}>
<Icon name="ios-contact" size={50} color="#4d8dd6" />
<View
style={{
flex: 1,
justifyContent: "flex-start",
alignItems: "flex-start",
marginHorizontal: 25
}}
>
<Text
style={{
color: "#000",
fontSize: 17
// marginHorizontal: 25
// alignSelf: "stretch"
}}
>
{item.username}
</Text>
{/* <Text
style={{
color: "#a1a1a1",
marginHorizontal: 35,
marginVertical: 5,
alignSelf: "stretch"
}}
numberOfLines={1}
lineBreakMode="tail"
>
{item.lastMssg.text}
</Text> */}
</View>
<Icon name="ios-chatboxes" size={25} color="#d6d6d6" />
</View>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center"
},
parent: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 25,
marginHorizontal: 15,
borderBottomWidth: 1,
borderBottomColor: "#eee"
}
});
export default ListChats;

You have used extra data props but the issue is flatlist does a shallow data comparison so when the length of users changes it won't affect the flatlist so replace it with this.state.users
extraData={this.state.users}
You can see in the documentation it says flatlist is implementation of PureCompoment and PureComponent does a shallow comparison thats the reason it is not re-rendering.
https://reactnative.dev/docs/flatlist#extradata

to force the flatlist re-render. just add a boolean lets say "forceUpdate" to your state and whenever an item is added to flatlist data toggle that boolean and pass that to the extra data of your FlatList instead of the state.

Related

React Native Flatlist element onPress not fired until List rendering is complete

I have a FlatList that receives (immutable) data of max. 50 elements and it renders in each list item Svg using react-native-svg.
Parts of the graphics are wrapped with a Pressable component for selecting the element.
Now the problem is, that I can't select any of the elements, until the FlatList went through all 50 items.
What I don't get is, that the offscreen items aren't even rendered, it's just the containers. Once it's all rendered, I can click the elements, the ripple effect shows and the event is fired.
Specs:
Expo # 46.0.0
React Native # 0.69.6
React # 18.0.0
Running with android via expo start --no-dev --minify then open in Expo Go
Reproduction:
import React, { useEffect, useState } from 'react'
import { FlatList } from 'react-native'
import { Foo } from '/path/to/Foo'
import { Bar } from '/path/to/Bar'
export const Overview = props => {
const [data, setData] = useState(null)
// 1. fetching data
useEffect(() => {
// load data from api
const loaded = [{ id: 0, type: 'foo' }, { id: 1, type: 'bar' }] // make a list of ~50 here
setData(loaded)
}, [])
if (!data?.length) {
return null
}
// 2. render list item
const onPressed = () => console.debug('pressed')
const renderListItem = ({ index, item }) => {
if (item.type === 'foo') {
return (<Foo key={`foo-${index}`} onPressed={onPressed} />)
}
if (item.type === 'bar') {
return (<Foo key={`bar-${index}`} onPressed={onPressed} />)
}
return null
}
// at this point data exists but will not be changed anymore
// so theoretically there should be no re-render
return (
<FlatList
data={data}
renderItem={renderListItem}
inverted={true}
decelerationRate="fast"
disableIntervalMomentum={true}
removeClippedSubviews={true}
persistentScrollbar={true}
keyExtractor={flatListKeyExtractor}
initialNumToRender={10}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={100}
getItemLayout={flatListGetItemLayout}
/>
)
}
}
// optimized functions
const flatListKeyExtractor = (item) => item.id
const flatListGetItemLayout = (data, index) => {
const entry = data[index]
const length = entry && ['foo', 'bar'].includes(entry.type)
? 110
: 59
return { length, offset: length * index, index }
}
Svg component, only Foo is shown, since Bar is structurally similar and the issue affects both:
import React from 'react'
import Svg, { G, Circle } from 'react-native-svg'
const radius = 25
const size = radius * 2
// this is a very simplified example,
// rendering a pressable circle
const FooSvg = props => {
return (
<Pressable
android_ripple={rippleConfig}
pressRetentionOffset={0}
hitSlop={0}
onPress={props.onPress}
>
<Svg
style={props.style}
width={size}
height={size}
viewBox={`0 0 ${radius * 2} ${radius * 2}`}
>
<G>
<Circle
cx='50%'
cy='50%'
stroke='black'
strokeWidth='2'
r={radius}
fill='red'
/>
</G>
</Svg>
</Pressable>
)
}
const rippleConfig = {
radius: 50,
borderless: true,
color: '#00ff00'
}
// pure component
export const Foo = React.memo(FooSvg)
The rendering performance itself is quite good, however I can't understand, why I need to wait up to two seconds, until I can press the circles, allthough they have already been rendered.
Any help is greatly appreciated.
Edit
When scrolling the list very fast, I get:
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. {"contentLength": 4740, "dt": 4156, "prevDt": 5142}
However, the Components are already memoized (PureComponent) and not very complex. There must be another issue.
Hardware
I cross tested with an iPad and there is none if the issues described. It seems to only occur on Android.
Please ignore grammatical mistakes.
This is the issue with FlatList. Flat list is not good for rendering a larger list at one like contact list. Flatlist is only good for getting data from API in church's like Facebook do. get 10 element from API and. then in the next call get 10 more.
To render. a larger number of items like contact list (more than 1000) or something like this please use https://bolan9999.github.io/react-native-largelist/#/en/
import React, {useRef, useState} from 'react';
import {
Image,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import {LargeList} from 'react-native-largelist-v3';
import Modal from 'react-native-modal';
import {widthPercentageToDP as wp} from 'react-native-responsive-screen';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
import fonts from '../constants/fonts';
import {moderateScale} from '../constants/scaling';
import colors from '../constants/theme';
import countries from '../Data/larger_countries.json';
const CountrySelectionModal = ({visible, setDefaultCountry, setVisible}) => {
const pressable = useRef(true);
const [country_data, setCountryData] = useState(countries);
const [search_text, setSearchText] = useState('');
const onScrollStart = () => {
if (pressable.current) {
pressable.current = false;
}
};
const onScrollEnd = () => {
if (!pressable.current) {
setTimeout(() => {
pressable.current = true;
}, 100);
}
};
const _renderHeader = () => {
return (
<View style={styles.headermainView}>
<View style={styles.headerTextBg}>
<Text style={styles.headerTitle}>Select your country</Text>
</View>
<View style={styles.headerInputBg}>
<TouchableOpacity
onPress={() => searchcountry(search_text)}
style={styles.headericonBg}>
<FontAwesome
name="search"
size={moderateScale(20)}
color={colors.textColor}
/>
</TouchableOpacity>
<TextInput
placeholder="Select country by name"
value={search_text}
placeholderTextColor={colors.textColor}
style={styles.headerTextInput}
onChangeText={text => searchcountry(text)}
/>
</View>
</View>
);
};
const _renderEmpty = () => {
return (
<View
style={{
height: moderateScale(50),
backgroundColor: colors.white,
flex: 1,
justifyContent: 'center',
}}>
<Text style={styles.notFoundText}>No Result Found</Text>
</View>
);
};
const _renderItem = ({section: section, row: row}) => {
const country = country_data[section].items[row];
return (
<TouchableOpacity
activeOpacity={0.95}
onPress={() => {
setDefaultCountry(country),
setSearchText(''),
setCountryData(countries),
setVisible(false);
}}
style={styles.renderItemMainView}>
<View style={styles.FlagNameView}>
<Image
source={{
uri: `https://zoobiapps.com/country_flags/${country.code.toLowerCase()}.png`,
}}
style={styles.imgView}
/>
<Text numberOfLines={1} ellipsizeMode="tail" style={styles.text}>
{country.name}
</Text>
</View>
<Text style={{...styles.text, marginRight: wp(5), textAlign: 'right'}}>
(+{country.callingCode})
</Text>
</TouchableOpacity>
);
};
const searchcountry = text => {
setSearchText(text);
const items = countries[0].items.filter(row => {
const result = `${row.code}${row.name.toUpperCase()}`;
const txt = text.toUpperCase();
return result.indexOf(txt) > -1;
});
setCountryData([{header: 'countries', items: items}]);
};
return (
<Modal
style={styles.modalStyle}
animationIn={'slideInUp'}
animationOut={'slideOutDown'}
animationInTiming={1000}
backdropOpacity={0.3}
animationOutTiming={700}
hideModalContentWhileAnimating={true}
backdropTransitionInTiming={500}
backdropTransitionOutTiming={700}
useNativeDriver={true}
isVisible={visible}
onBackdropPress={() => {
setVisible(false);
}}
onBackButtonPress={() => {
setVisible(false);
}}>
<LargeList
showsHorizontalScrollIndicator={false}
style={{flex: 1, padding: moderateScale(10)}}
onMomentumScrollBegin={onScrollStart}
onMomentumScrollEnd={onScrollEnd}
contentStyle={{backgroundColor: '#fff'}}
showsVerticalScrollIndicator={false}
heightForIndexPath={() => moderateScale(49)}
renderIndexPath={_renderItem}
data={country_data}
bounces={false}
renderEmpty={_renderEmpty}
renderHeader={_renderHeader}
headerStickyEnabled={true}
initialContentOffset={{x: 0, y: 600}}
/>
</Modal>
);
};
export default CountrySelectionModal;
const styles = StyleSheet.create({
modalStyle: {
margin: moderateScale(15),
borderRadius: moderateScale(10),
overflow: 'hidden',
backgroundColor: '#fff',
marginVertical: moderateScale(60),
justifyContent: 'center',
},
headermainView: {
height: moderateScale(105),
backgroundColor: '#fff',
},
headerTextBg: {
height: moderateScale(50),
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
headerTitle: {
textAlign: 'center',
fontFamily: fonts.Bold,
fontSize: moderateScale(16),
color: colors.textColor,
textAlignVertical: 'center',
},
headerInputBg: {
height: moderateScale(40),
borderRadius: moderateScale(30),
overflow: 'hidden',
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: moderateScale(10),
backgroundColor: colors.inputbgColor,
flexDirection: 'row',
},
headericonBg: {
backgroundColor: colors.inputbgColor,
alignItems: 'center',
justifyContent: 'center',
width: moderateScale(40),
height: moderateScale(40),
},
headerTextInput: {
backgroundColor: colors.inputbgColor,
height: moderateScale(30),
flex: 1,
paddingTop: 0,
includeFontPadding: false,
fontFamily: fonts.Medium,
color: colors.textColor,
paddingBottom: 0,
paddingHorizontal: 0,
},
notFoundText: {
fontFamily: fonts.Medium,
textAlign: 'center',
fontSize: moderateScale(14),
textAlignVertical: 'center',
color: colors.textColor,
},
renderItemMainView: {
backgroundColor: colors.white,
flexDirection: 'row',
alignSelf: 'center',
height: moderateScale(43),
alignItems: 'center',
justifyContent: 'space-between',
width: wp(100) - moderateScale(30),
},
FlagNameView: {
flexDirection: 'row',
justifyContent: 'center',
paddingLeft: moderateScale(12),
alignItems: 'center',
},
imgView: {
height: moderateScale(30),
width: moderateScale(30),
marginRight: moderateScale(10),
borderRadius: moderateScale(30),
},
text: {
fontSize: moderateScale(13),
color: colors.textColor,
marginLeft: 1,
fontFamily: fonts.Medium,
},
});

How can I store the value of TextInput to local storage and get them when the app starts in react native?

I'm making a to-do list app. I need to store the input value locally or somewhere so that I can show them in the app even if the app is opened after closing once. Now when I'm closing the app and restarting all the previous values is being vanished. But I want to keep the previous data and If new data is given that will also be stored if I don't delete that manually. How can I solve this problem? I'm using expo.
Here is my code:
import React, { useState } from 'react';
import { View, Text, StyleSheet, Button, FlatList, TouchableOpacity, TextInput, ScrollView } from 'react-native';
import { MaterialIcons } from '#expo/vector-icons'
import Line from './Line';
function App() {
//storing data in array
const [initialElements, changeEl] = useState([
]);
const [value, setValue] = useState('')
const [idx, incr] = useState(1);
const [exampleState, setExampleState] = useState(initialElements);
//add button
const addElement = () => {
if (value != '') {
var newArray = [...initialElements, { id: idx, title: value }];
incr(idx + 1);
setExampleState(newArray);
changeEl(newArray);
}
}
//delete button
const delElement = (id) => {
let newArray = initialElements.filter(function (item) {
return item.id !== id
})
setExampleState(newArray);
changeEl(newArray);
}
//showing item
const Item = ({ title, id }) => (
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
<View style={{ borderWidth: 2, margin: 5, flex: 1, padding: 10, borderRadius: 10 }}>
<TouchableOpacity onPress={() => { alert(title) }}>
<Text >{title}</Text>
</TouchableOpacity>
</View>
<TouchableOpacity onPress={() => delElement(id)}>
<MaterialIcons name='delete' size={24} />
</TouchableOpacity>
</View>
);
//calling item for render
const renderItem = ({ item }) => {
<Item title={item.title} id={item.id} />
}
return (
<View style={styles.container}>
<View style={{ alignItems: 'center', marginBottom: 10 }}>
<Text style={{ fontSize: 20, fontWeight: '500' }}> To <Text style={{ color: 'skyblue', fontWeight: '900' }}>Do</Text> List</Text>
<Line />
</View>
<View>
<TextInput onChangeText={text => setValue(text)} placeholder="Enter to Todo" style={{ borderWidth: 2, borderRadius: 10, backgroundColor: 'skyblue', height: 40, paddingLeft: 10, borderColor: 'black' }}></TextInput>
</View>
<View style={{ alignItems: 'center' }}>
<TouchableOpacity onPress={() => addElement()} style={{ marginTop: 10 }}>
<View style={{ backgroundColor: 'black', width: 70, height: 40, justifyContent: 'center', borderRadius: 10 }}>
<Text style={{ color: 'white', textAlign: 'center' }}>Add</Text>
</View>
</TouchableOpacity>
</View>
<FlatList
style={{ borderWidth: 2, padding: 10, flex: 1, backgroundColor: '#EEDDD0', marginTop: 10 }}
data={exampleState}
renderItem={renderItem}
keyExtractor={item => item.id.toString()
}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20,
margin: 5,
}
})
export default App;
React Native provides a local storage option called AsyncStorage. You might use asyncstorage to save the data locally on the device and get that data inside useEffect() hook on startup.
You can find more about AsyncStorage here.
Although this is deprecated and now community package " #react-native-async-storage/async-storage " is used. The implementation remains the same.

How to save route.params with asyncstorage?

Srry if the title makes no sense. Don't know a better title.
How can I save route.params items that I pass to my second screen using AsyncStorage?
In my first screen i have a bunch of data in a FlatList that can be opened with a Modal. Inside that Modal I have a TouchableOpacity that can send the data thats inside the Modal to my second screen. The data that has been passed to the second screen is passed to a FlatList. The data in the FlatList should be saved to AsyncStorage. Tried alot of things getting this to work, but only getting warning message
undefined. Code below is the most recent progress.
Using React Navigation V5.
FIRST SCREEN
const [masterDataSource, setMasterDataSource] = useState(DataBase);
const [details, setDetails] = useState('');
<TouchableOpacity
onPress={() => {
const updated = [...masterDataSource];
updated.find(
(item) => item.id === details.id,
).selected = true;
setMasterDataSource(updated);
navigation.navigate('cart', {
screen: 'cart',
params: {
items: updated.filter((item) => item.selected),
},
});
setModalVisible(false);
}}>
<Text>Add to cart</Text>
</TouchableOpacity>
SECOND SCREEN
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity } from 'react-native';
import { useTheme } from '../Data/ThemeContext';
import AsyncStorage from '#react-native-async-storage/async-storage';
import Ionicons from 'react-native-vector-icons/Ionicons';
export default function ShoppingList({ route, navigation }) {
const [shoppingList, setShoppingList] = useState([]);
const { colors } = useTheme();
const todo = () => {
alert('Todo');
};
useEffect(() => {
restoreShoppingListAsync();
}, []);
const shoppingListAsync = () => {
const shoppingList = route.params && route.params.items;
setShoppingList(list);
storeShoppingList(list);
};
const asyncStorageKey = '#ShoppingList';
const storeShoppingListAsync = (list) => {
const stringifiedList = JSON.stringify(list);
AsyncStorage.setItem(asyncStorageKey, stringifiedList).catch((err) => {
console.warn(err);
});
};
const restoreShoppingListAsync = () => {
AsyncStorage.getItem(asyncStorageKey)
.then((stringifiedList) => {
console.log(stringifiedList);
const parsedShoppingList = JSON.parse(stringifiedList);
if (!parsedShoppingList || typeof parsedShoppingList !== 'object')
return;
setShoppingList(parsedShoppingList);
})
.then((err) => {
console.warn(err);
});
};
const RenderItem = ({ item }) => {
return (
<View>
<TouchableOpacity
style={{
marginLeft: 20,
marginRight: 20,
elevation: 3,
backgroundColor: colors.card,
borderRadius: 10,
}}>
<View style={{ margin: 10 }}>
<Text style={{ color: colors.text, fontWeight: '700' }}>
{item.name}
</Text>
<Text style={{ color: colors.text }}>{item.gluten}</Text>
<Text style={{ color: colors.text }}>{item.id}</Text>
</View>
</TouchableOpacity>
</View>
);
};
const emptyComponent = () => {
return (
<View style={{ alignItems: 'center' }}>
<Text style={{ color: colors.text }}>Listan är tom</Text>
</View>
);
};
const itemSeparatorComponent = () => {
return (
<View
style={{
margin: 3,
}}></View>
);
};
return (
<View
style={{
flex: 1,
}}>
<View
style={{
padding: 30,
backgroundColor: colors.Textinput,
elevation: 12,
}}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Ionicons name="arrow-back-outline" size={25} color="#fff" />
</TouchableOpacity>
<Text style={{ color: '#fff', fontSize: 20 }}>Inköpslista</Text>
<TouchableOpacity>
<Ionicons
name="trash-outline"
size={25}
color="#fff"
onPress={() => todo()}
/>
</TouchableOpacity>
</View>
</View>
<View style={{ flex: 1, marginTop: 30 }}>
<FlatList
data={shoppingList}
renderItem={RenderItem}
ListEmptyComponent={emptyComponent}
ItemSeparatorComponent={itemSeparatorComponent}
initialNumToRender={4}
maxToRenderPerBatch={5}
windowSize={10}
removeClippedSubviews={true}
updateCellsBatchingPeriod={100}
showsVerticalScrollIndicator={true}
contentContainerStyle={{ paddingBottom: 20 }}
/>
</View>
</View>
);
}
As you are using async storage to maintain the cart.
I would suggest an approach as below
Update the asyn storage when new items are added to or removed from the cart
Retrieve the items from the cart screen and show the items there
Before you navigate store the items like below
AsyncStorage.setItem(
'Items',
JSON.stringify(updated.filter((item) => item.selected))
).then(() => {
navigation.navigate('Cart', {
items: updated.filter((item) => item.selected),
});
});
The cart screen would be something like below
function Cart({ navigation, route }) {
const [data,setData]=useState([]);
React.useEffect(() => {
async function fetchMyAPI() {
const value = await AsyncStorage.getItem('Items');
setData(JSON.parse(value));
}
fetchMyAPI();
}, []);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Go back" onPress={() => navigation.goBack()} />
<FlatList
data={data}
renderItem={RenderItem}
/>
</View>
);
}
Working Example
https://snack.expo.io/#guruparan/cartexample

Pass JSON data into another screen

1
I am a beginner and still learning to react native.
In my react native App, I have 2 screens. In the first page, I have JSON data ; I want to pass this JSON data to the next page.
I used react-navigation for navigating between pages. I need to passed each parameter for a new book screen for each book.
But I couldn't figure out, how to pass JSON data to next page! In BookScreen.js the function "getParam" is not been seen.
First Screen: ExploreScreen.js
import React, { useState, useEffect } from "react";
import {
View,
Text,
StyleSheet,
FlatList,
Image,
TouchableOpacity,
} from "react-native";
export default function ExploreScreen({ navigation, route }) {
const [data, setData] = useState([]);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
await fetch(
"http://www.json-generator.com/api/json/get/bTvNJudCPS?indent=2"
)
.then((response) => response.json())
.then((receivedData) => setData(receivedData));
};
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.book}
onPress={() => navigation.navigate("Book", item)}
>
<Image
style={styles.bookImage}
source={{ uri: item.book_image }}
></Image>
<View>
<Text style={styles.bookTitleText}>{item.title}</Text>
<Text style={styles.bookAuthor}>{item.author}</Text>
<Text style={styles.bookGenre}>
<Text styles={styles.gen}>Genul: </Text>
{item.genre_id}
</Text>
</View>
</TouchableOpacity>
)}
></FlatList>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
alignSelf: "center",
paddingVertical: "15%",
},
book: {
flex: 1,
flexDirection: "row",
marginBottom: 3,
},
bookImage: {
width: 100,
height: 100,
margin: 5,
},
bookTitleText: {
color: "#8B0000",
fontSize: 15,
fontStyle: "italic",
fontWeight: "bold",
},
bookAuthor: {
color: "#F41313",
},
});
Second Screen: BookScreen.js
import React from "react";
import { View, Text, StyleSheet } from "react-native";
export default function BookScreen({ navigation, route }) {
const { item } = route.params;
return (
<View style={styles.container}>
<Text style={styles.text}>{navigation.getParam("name")}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
alignSelf: "center",
paddingVertical: "100%",
},
text: {
fontSize: 20,
},
});
In your BookScreen, change it to the following:
export default function BookScreen({ navigation, route }) {
const { item } = route.params;
return (
<View style={styles.container}>
<Text style={styles.text}>{item.name}</Text>
</View>
);
}
Edit:
I think you should pass the data like this:
navigation.navigate('Book', {item: item});

Flatlist does not render if have one object in data?

I have some issue with Flatlist, I have an array of objects I got them from DB,
and save them to state, now when I log the this.state.providers in render() method or in the callback func after setState I got the valid Object like this
[{username: "Test", key: "53HoDga6aYhHsV5pCi5sx6LGbx42"}]
but when I passed these object into data prop in <Flatlist data={this.state.providers} /> the flatlist not rendering!
but when I add the object in the data prop manual like this
<Flatlist data={[{username: "Test One", key: "53HoDga6aYhHsV5pCi5sx6LGbx42"}]}
the flatlist work very well,
But I'm sure the code is correct because I added them into other project and work very well!!
Edit
when I pass tow object into the array the flatlist work!!
so how to handle it if I got one object from DB!
Code
import React, { Component } from "react";
import firebase from "react-native-firebase";
import Icon from "react-native-vector-icons/Ionicons";
import {
View,
Text,
StyleSheet,
FlatList,
TouchableOpacity,
Dimensions
} from "react-native";
class ListChats extends Component {
constructor(props) {
super(props);
this.state = {
providers: []
};
}
_chatList = () => {
let currentUser = firebase.auth().currentUser.uid;
let ref = firebase.database().ref(`Messages/${currentUser}`);
let providersKeys = [];
ref.once("value").then(snapshot => {
snapshot.forEach(childsnap => {
console.log(childsnap.key);
providersKeys.push(childsnap.key);
});
let usernames = [];
providersKeys.forEach(key => {
firebase
.database()
.ref("users")
.child(key)
.once("value")
.then(providersShot => {
let username = providersShot.val().username;
usernames.push({ username: username, key: key });
});
});
this.setState({ providers: usernames }, () =>
console.log(this.state.providers)
);
});
};
componentDidMount() {
this._chatList();
}
_listEmptyComponent = () => {
return (
<View style={styles.container}>
<Text style={{ alignSelf: "center" }}>No Chats Found :O</Text>
</View>
);
};
render() {
console.log(this.state.providers); // I got [{username: "Test", key: "53HoDga6aYhHsV5pCi5sx6LGbx42"}]
return (
<View style={{ flex: 1 }}>
<FlatList
key={Math.random() * 1000}
data={this.state.providers}
contentContainerStyle={{ flexGrow: 1 }}
ListEmptyComponent={this._listEmptyComponent()}
keyExtractor={item => item.key.toString()}
renderItem={({ item }) => {
console.log("item", item);
return (
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate("ChatDetails", {
Key: item.key,
providerName: item.username
})
}
>
<View style={styles.parent}>
<Icon name="ios-contact" size={50} color="#4d8dd6" />
<View
style={{
flex: 1,
justifyContent: "flex-start",
alignItems: "flex-start",
marginHorizontal: 25
}}
>
<Text
style={{
color: "#000",
fontSize: 17
}}
>
{item.username}
</Text>
</View>
<Icon name="ios-chatboxes" size={25} color="#d6d6d6" />
</View>
</TouchableOpacity>
);
}}
// keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
parent: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 25,
marginHorizontal: 15,
borderBottomWidth: 1,
borderBottomColor: "#eee"
}
});
export default ListChats;
You should add extraData property to FlatList:
extraData={this.state}
In here your using usernames.push method to add the data objects to array, using this method will not indicate the state about a need of a re-render even if the state is updated with a new array.
For this usual coding pattern is using the spread operator
this.setState({ providers: [...usernames] })
Use "extraData" props of FlatList.

Categories

Resources