So I have a React Native Flatlist. Then I have a useState which fires when I click on a item in my flatlist. The problem I am facing to right now is that if I click on a item and the useState() fires every item is going to rerender. This is bad because it makes the app extremly slow.
Is there any way to set my State without the FlatList gets rerendered but another view appears (because of the useState())??
My code looks like this:
import React, {useState} from "react"
import {View, Flatlist, Text} from "react-native"
export default const App = () => {
const [x, setX] = useState(false)
return(
<View>
<FlatList
data = {
["-",
"--",
"---",
"----",
"-----",
"------",
"-------",
"--------",
"---------"]
}
renderItem={({item}) => {(
<View>
<TouchableOpacity onPress={() => setX(true)}>
<Text style={{padding: 10,}}>{item}</Text>
</TouchableOpacity>
</View>
)}}
/>
{x == true ? <Button title="It works" onPress={() => console.log("Everything is working")} /> : <View />}
</View>
)
}
In this example the data list is very small. If it get's bigger you really can see a difference
Here are things to consider to make sure that the FlatList won't re-render on state change (see code below):
Make sure that the data being passed is stored or memoized somehow.
Make sure that the callbacks are also memoized.
import React, {useState} from "react"
import {View, Flatlist, Text} from "react-native"
export default const App = () => {
// See 1. above
const [data, setData] = useState([
"-",
"--",
"---",
"----",
"-----",
"------",
"-------",
"--------",
"---------",
]);
const [x, setX] = useState(false)
// See 2. above
const renderItem = useCallback(({ item }) => (
<View>
<TouchableOpacity onPress={() => setX(true)}>
<Text style={{padding: 10,}}>{item}</Text>
</TouchableOpacity>
</View>
), []);
return(
<View>
<FlatList
data={data}
renderItem={renderItem}
/>
{x == true ? <Button title="It works" onPress={() => console.log("Everything is working")} /> : <View />}
</View>
)
}
Here's an idea with memo and separate components:
const App = () => {
const [x,setX] = useState(false);
return (
<View>
<MyFlatList setX={setX}/>
{x == true ? <Button title="It works" onPress={() => console.log("Everything is working")} /> : <View />}
</View>
)
}
and then:
const MyFlatList = React.memo(({setX}) => {
return (
<FlatList
data = {
["-",
"--",
"---",
"----",
"-----",
"------",
"-------",
"--------",
"---------"]
}
renderItem={({item}) => {(
<View>
<TouchableOpacity onPress={() => setX(true)}>
<Text style={{padding: 10,}}>{item}</Text>
</TouchableOpacity>
</View>
)}}
/>
);
});
crawler's answer is very good with recommendations of memoizing the data and the callback, as well.
Related
How can I add a button inside flatlist, whenever I tried to add a button then I am getting multiple buttons inside flatlist.
I want only one button which is scrollable with flatlist.
and if I add a button outside flatlist then it's not scrolling, it get fixed below the flatlist, only flatlist data scroll but the button not scroll with flatlist. How can I solve this issue? Really appreciate your support.
import React, { useState, useEffect } from 'react';
import {View, Button, Text, FlatList, StyleSheet, Pressable, TouchableOpacity} from 'react-native'
import {firebase} from '../config';
const Testing = ({ navigation }) =>{
const [users, setUsers] = useState([]);
const todoRef = firebase.firestore().collection('testing');
useEffect(() => {
todoRef.onSnapshot(
querySnapshot => {
const users = []
querySnapshot.forEach((doc) => {
const { one, two, three, four, five
} = doc.data()
users.push({
id: doc.id,
one, two, three, four, five
})
})
setUsers(users)
}
)
}, [])
return (
<View style={{ flex:1,}}>
<FlatList
style={{height: '100%'}}
data={users}
numColumns={1}
renderItem={({item}) => (
<Pressable>
<View>
<View>
<Text style={[styles.card, styles.surah]}>{item.one}</Text>
<Text style={styles.card}>{item.two}</Text>
<Text style={styles.text}>{item.three}</Text>
<Text style={styles.cardTwo}>{item.four}</Text>
<Text style={styles.text}>{item.five}</Text>
</View>
</View>
// I tried to add here button but it's not worked
</Pressable>
)}/>
// I also tried to add here button but it's not worked
</View>
);}
export default Testing;
You could implement this with the help of the index parameter of the renderItem function.
renderItem={({item, index}) => (
<View>
<View>
<Text style={[styles.card, styles.surah]}>{item.one}</Text>
<Text style={styles.card}>{item.two}</Text>
<Text style={styles.text}>{item.three}</Text>
<Text style={styles.cardTwo}>{item.four}</Text>
<Text style={styles.text}>{item.five}</Text>
</View>
{
index === users.length - 1 && <Pressable onPress={...}>...</Pressable>
}
</View>
)}
The above adds a component, in this case a Pressable at the end of the last item. If you want the last item to be pressable, then you can achieve this using the same pattern, but by wrapping the last component inside a pressable.
const InnerComponent = () => {
return <View>
<Text style={[styles.card, styles.surah]}>{item.one}</Text>
<Text style={styles.card}>{item.two}</Text>
<Text style={styles.text}>{item.three}</Text>
<Text style={styles.cardTwo}>{item.four}</Text>
<Text style={styles.text}>{item.five}</Text>
</View>
}
...
renderItem={({item, index}) => (
<View>
{
index === users.length - 1 ? <Pressable onPress={...}>
<InnerComponent />
</Pressable> : <InnerComponent />
}
</View>
)}
//I am rendering an API response with the help of FLatlist but when I press the expand option it will open all the accordions.................
import { View, Text, StyleSheet, FlatList } from 'react-native'
import React, { useState } from 'react'
import SearchBox from '../../components/SearchBox/SearchBox'
import { ListItem, Icon, Slider } from '#rneui/themed'
import { useSelector } from 'react-redux'
import { getAllPackages } from '../../feature/packageSlice'
const Rounds = () => {
const [expanded, setExpanded] = useState(false)
const pack = useSelector(getAllPackages)
//flatlist render item
const renderItem = ({ item }) => {
return (
<ListItem.Accordion
content={
<>
<ListItem.Content>
<ListItem.Title style={styles.header}>
{item.name}
</ListItem.Title>
</ListItem.Content>
</>
}
isExpanded={expanded}
onPress={() => {
setExpanded(!expanded)
}}
>
<View style={styles.card}>
<Text style={styles.font}>Water Supply Pressure</Text>
</View>
</View>
</ListItem.Accordion>
)
}
//main render
return (
<View>
<FlatList
data={pack}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
</View>
</View>
)
}
export default Rounds
I want to open up the selected accordion only how can I achieve that, please help thanks...................................................................................................................................
If i understand your code correctly and this is one component (not fraction of few) your problem is following:
const [expanded, setExpanded] = useState(false)
This state variable is on top of your parent Component, so each rendered item points to it.
Therefore if you change it from any of your ListItem.Accordion, it will affect all of them.
BUT
If you change your renderItem to render Component. like this:
const renderItem = ({ item }) => {
return (
<AccordionListItem item={item}/>
)
}
Then you can move this state inside AccordionListItem itself, so it will create unique instance for each unique instance of component.
//imports
import React from ...
const AccordionListItem = ({item}) => {
const [expanded, setExpanded] = useState(false) <========= !
return (
<ListItem.Accordion
content={
<>
<ListItem.Content>
<ListItem.Title style={styles.header}>
{item.name}
</ListItem.Title>
</ListItem.Content>
</>
}
isExpanded={expanded}
onPress={() => {
setExpanded(!expanded)
}}
>
<View style={styles.card}>
<Text style={styles.font}>Water Supply Pressure</Text>
</View>
</View>
</ListItem.Accordion>
)
}
export default AccordionListItem ;
I'm trying to use Autocomplete in React-native but I'm obviously doing something wrong.
Here a minimal example of code showing what I'm doing:
import React, {useState, useEffect} from 'react';
import {
SafeAreaView,
StyleSheet,
TextInput,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Autocomplete from 'react-native-autocomplete-input';
const DATA =
[{"id": 1,"title": "test1"},{"id": 2,"title": "test2"},{"id": 3,"title": "test3"}]
const App = () => {
const [query, setQuery] = useState('');
return (
<SafeAreaView style={{flex: 1}}>
<View style={styles.container}>
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
data={DATA}
value={query}
onChangeText={setQuery}
renderItem={({item}) => (
<TouchableOpacity
onPress={() => {}}>
<Text>
{item.title}
</Text>
</TouchableOpacity>
)}
/>
</View>
</SafeAreaView>
);
};
I reduced the code to the very minimum.
However, I keep getting error:
"Objecs are not valid as a React child (found: object with keys {id,title}"
It seems I'm missing something very obvious about renderItem (I guess), but since I'm stuck since a few hours, another eye could spot what I'm doing wrong.. any help would be appreciated, thanks.
Your destructuring was wrong. do like below {id,title}. renderItem is array of iteration so its look like object.
renderItem={({title,id}) => (
<TouchableOpacity
onPress={() => {}}>
<Text>
{title}
</Text>
</TouchableOpacity>
)}
I actually found the answer.
It seems, I haven't read correctly the list of props for Autocomplete: in fact, renderItem must be passed inside an option called "flatListProps".
Correct code is below:
import React, {useState, useEffect} from 'react';
import {
SafeAreaView,
StyleSheet,
TextInput,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Autocomplete from 'react-native-autocomplete-input';
const DATA =
[{"id": 1,"title": "test1"},{"id": 2,"title": "test2"},{"id": 3,"title": "test3"}]
const App = () => {
const [query, setQuery] = useState('');
return (
<SafeAreaView style={{flex: 1}}>
<View style={styles.container}>
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
data={DATA}
value={query}
onChangeText={setQuery}
flatListProps={{
keyboardShouldPersistTaps: 'always',
keyExtractor: (item) => item.id,
renderItem: ( ({item}) => (
<TouchableOpacity
onPress={() => {}}>
<Text>
{item.title}
</Text>
</TouchableOpacity>
))
}}
/>
</View>
</SafeAreaView>
);
};
renderItem={({ item, i }) => (
<TouchableOpacity key={i} onPress={() => ()}>
<Text>{item.label}</Text>
</TouchableOpacity>
)}
You have to pass the index to key.
The data being fetched from the api is needed for other purposes in the modal. How do i pass data: {currency.data.prices[index].instrument} {currency.data.prices[index].closeoutAsk} {currency.data.prices[index].closeoutBid} that is in a component to a modal that is in the same component. Below is the code:
//HomeScreen
import React, {useContext, useState} from 'react'
import { Text, View, ScrollView, TouchableOpacity, Modal, TextInput } from 'react-native'
import {ListItem, Card, Button, Icon} from 'react-native-elements'
//import CurrencyPair from '../../CurrencyPair'
import {firebase} from '../../../firebase/config'
import {CurrencyContext} from '../../../context/Context'
import styles from '../LoginScreen/styles'
function HomeScreen() {
const currency = useContext(CurrencyContext);
const [modalopen, setModalOpen] = useState(false)
return (
<ScrollView>
<Modal
visible={modalopen}
animationType={"fade"}
>
<View style={styles.modal}>
<View>
<Text style={{textAlign: "center", fontWeight: "bold"}}>
CreateAlert
</Text>
<TouchableOpacity style={styles.button} onPress={() => setModalOpen(false)}>
<Text style={styles.buttonTitle}>OK</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
<Card>
<Text style={{textAlign: "center"}}>
Welcome
</Text>
<Button title="Sign Out" type="outline" onPress ={() => firebase.auth().signOut()}/>
<Button title="My Alerts" onPress ={() =>navigation.navigate("AlertScreen") }/>
</Card>
<View>
{currency.data.prices && currency.data.prices.map((prices, index) => {
return (
<ListItem
key={index}
onPress = {() => setModalOpen(true)}
bottomDivider>
<ListItem.Content>
<ListItem.Title>
{currency.data.prices[index].instrument} {currency.data.prices[index].closeoutAsk} {currency.data.prices[index].closeoutBid}
</ListItem.Title>
</ListItem.Content>
</ListItem>
)
})
}
</View>
</ScrollView>
)
}
export default HomeScreen
//Context
import React, {createContext, useState, useEffect}from 'react'
import {ActivityIndicator} from 'react-native'
import axios from '../utils/axios'
const CurrencyContext = createContext();
const CurrencyProvider =(props) => {
const [data, setData] = useState([])
const [isLoading, setIsloading] = useState(true)
useEffect(() => {
const interval = setInterval(() => {
const fetchpairs = async() => {
const results = await axios.get('/v3/accounts/101-004-14328428-002/pricing?instruments=AUD_CAD%2CAUD_CHF%2CAUD_JPY%2CAUD_NZD%2CAUD_USD%2CCAD_CHF%2CCAD_JPY%2CCHF_JPY%2CEUR_AUD%2CEUR_CAD%2CEUR_CHF%2CEUR_GBP%2CEUR_NOK%2CEUR_NZD%2CEUR_USD%2CGBP_AUD%2CGBP_CAD%2CGBP_CHF%2CGBP_USD%2CGBP_JPY%2CNZD_CAD%2CNZD_CHF%2CNZD_JPY%2CUSD_CAD%2CUSD_JPY%2CUSD_CHF%2CUSD_ZAR%2CUSD_MXN')
setData(results.data)
setIsloading(false)
}
fetchpairs()
},1000)
}, []);
if(isLoading) {
return (
<ActivityIndicator size="large"/>
)
}else
return (
<CurrencyContext.Provider
value={{
data,
setData,
isLoading,
setIsloading
}}>
{props.children}
</CurrencyContext.Provider>
)
}
export {CurrencyProvider, CurrencyContext}
you can create another state variable to store the clicked index.
const [clickedIndex, setClickedIndex] = useState(0);
then use that in the onPress event.
onPress = {() => {setModalOpen(true);setClickedIndex(index);}
then you can use this same index to display what you want in the modal.
<Modal
visible={modalopen}
animationType={"fade"}
>
<View style={styles.modal}>
<View>
<Text style={{textAlign: "center", fontWeight: "bold"}}>
{currency.data.prices[clickedIndex].instrument}
</Text>
<TouchableOpacity style={styles.button} onPress={() => setModalOpen(false)}>
<Text style={styles.buttonTitle}>OK</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
I updated my code thanks to your help.
When I launch the app with Expo, the opening works but I lost my scan icon which does not appear in my screen.
This icon appeared previously.
The idea is to scan some barcodes in order to display relevant data stemming from products.
Here is my new code:
import React, { useState, useEffect } from "react";
import {
StyleSheet,
Text,
View,
FlatList,
Button,
AsyncStorage,
} from "react-native";
import { useNavigation } from "#react-navigation/core";
import { TouchableOpacity } from "react-native-gesture-handler";
import { FontAwesome5 } from "#expo/vector-icons";
import { MaterialCommunityIcons } from "#expo/vector-icons";
import { ActivityIndicator } from "react-native-paper";
function ProductsScreen() {
const navigation = useNavigation();
const [data, setData] = useState([]);
const [isLoading, setisLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
const data = await AsyncStorage.getItem("userData");
setData(data);
setisLoading(false);
};
fetchData();
}, []);
console.log(data);
return isLoading ? (
<ActivityIndicator />
) : (
<>
{data ? (
<FlatList
data={dataArray}
keyExtractor={(item) => item.name}
renderItem={({ item }) => (
<>
<Text>{item.brand}</Text>
<View style={styles.scan}>
<MaterialCommunityIcons
name="barcode-scan"
size={40}
color="black"
onPress={() => {
navigation.navigate("CameraScreen");
}}
/>
</View>
</>
)}
/>
) : null}
</>
);
}
export default ProductsScreen;
I would appreciate your comments please.
You could use ? (optional chaining) to confirm data doesnt yield to undefined before mapping.
data?.map((data, index) => {return <>....</>}
You need to return from data.map function to render the array items
return isLoading ? (
<ActivityIndicator />
) : (
<>
{data?.map((data, index) => {
return <View key ={index}>
<Text> {data.products_name_fr} </Text>
<Text> {data.brands} </Text>
<Text> {data.image_url} </Text>
<View style={styles.scan}>
<MaterialCommunityIcons
name="barcode-scan"
size={40}
color="black"
onPress={() => {
navigation.navigate("CameraScreen");
}}
/>
</View>
</View>;
})}
</>
);
Or short-hand of return
return isLoading ? (
<ActivityIndicator />
) : (
<>
data?.map((data, index) => (
<View key ={index}>
<Text> {data.products_name_fr} </Text>
<Text> {data.brands} </Text>
<Text> {data.image_url} </Text>
<View style={styles.scan}>
<MaterialCommunityIcons
name="barcode-scan"
size={40}
color="black"
onPress={() => {
navigation.navigate("CameraScreen");
}}
/>
</View>
</View>;
))
</>
);
I changed my code like this but I have the same error. Besides, the part of code which begins from: const styles=Stylesheet.create seems to be not active
import React, { useState, useEffect } from "react";
import { StyleSheet, Text, View, Button, AsyncStorage } from "react-native";
import { useNavigation } from "#react-navigation/core";
import { TouchableOpacity } from "react-native-gesture-handler";
import { FontAwesome5 } from "#expo/vector-icons";
import { MaterialCommunityIcons } from "#expo/vector-icons";
import { ActivityIndicator } from "react-native-paper";
import axios from "axios";
function ProductsScreen() {
const navigation = useNavigation();
const [data, setData] = useState([]);
const [isLoading, setisLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
const data = await AsyncStorage.getItem("userData");
setisLoading(false);
setData(data);
};
fetchData();
}, []);
return isLoading ? (
<ActivityIndicator />
) : (
<>
{data?.map((data, index) => {
return (
<>
key ={index}
<Text> {data.products_name_fr} </Text>
<Text> {data.brands} </Text>
<Text> {data.image_url} </Text>
<View style={styles.scan}>
<MaterialCommunityIcons
name="barcode-scan"
size={40}
color="black"
onPress={() => {
navigation.navigate("CameraScreen");
}}
/>
</View>
</>
);
})}
</>
);
const styles = StyleSheet.create({
products: {
alignItems: "center",
justifyContent: "center",
},
scan: {
marginLeft: 30,
position: "absolute",
bottom: 0,
right: 20,
marginBottom: 60,
marginRight: 30,
padding: 10,
borderRadius: 10,
backgroundColor: "#ff9234",
},
});
}
export default ProductsScreen;
I changed a little bit my code and I got another type of error : Invariant violation: Text strings must be rendered within a component. I will really appreciate your comments and support to fix this
return isLoading ? (
<ActivityIndicator />
) : (
<>
data?.map((data, index) => (
<>
<Text> {data.products_name_fr} </Text>
<Text> {data.brands} </Text>
<Text> {data.image_url} </Text>
<View style={styles.scan}>
<MaterialCommunityIcons
name="barcode-scan"
size={40}
color="black"
onPress={() => {
navigation.navigate("CameraScreen");
}}
/>
</View>
</>
))
</>
);
}
In the useEffect, set the data as array. Example
const = [data, setData] = useState([]); // do this in your state
setData([data]); //do this in your useEffet hook