I'm really struggling to figure out why i can't get my FlatList to render items the way that I would expect.
I'm using react-query in order to fetch a data array. I pass this array into my FlatList to be rendered, then I use a render function to render a list item. However my flat list completely ignores all of the item's padding and margin, sometimes.
Here's a snippet to better explain what I'm trying to do:
function Component() {
data = [
{a: true, otherStuff},
{b: true, otherStuff}
]
function renderItem({item, index}) {
if (item.a) {
return <ListItemA/>
}
if (item.b) {
return <ListItemB/>
}
render(
<FlatList
renderitem={renderItem}
data={data}
/>
)
}
if you will upload the components code, it will be more clear.
try to use item separator and add spacing there
https://reactnative.dev/docs/flatlist#itemseparatorcomponent a
Related
I have a problem where useState updates the state but does not show the changes until I refresh the app. First, I declare an array called sampleFriends, made up of objects with fields "name", "location", and "picture" (each element of the array looks similar to: {name: 'John', location: 'Boston', picture: TestImage}).
Then, I have the following useState:
const [selectedFriends, setSelectedFriends] = useState([])
At some point, I successfully render
sampleFriends.map(({ name, location, image }, index) => (
<NewMsgTableRow
name={name}
index={index}
location={location}
image={image}
onPress={() => selectFriend(name)}
/>
))
And I also have this function right above
const selectFriend = name => {
// if the friend is not already selected
if (!selectedFriends.find(e => e === name)) {
const newFriends = selectedFriends
newFriends.push(name)
setSelectedFriends(newFriends)
}
}
The component NewMsgTableRow has a button that uses onPress
<TouchableOpacity
onPress={onPress}
>
So, I want to render selectedFriends as soon as they are selected (the TouchableOpacity is touched and thus the state updates). However, when I click the button, nothing shows up until I edit and save my code and it refreshes automatically. It was my understanding that useState rerendered the components as soon as it was updated, but it is not happening in this case and I can't figure out why. I've been reading that it is async and that it does not change it instantly, but I don't know how to make it work. Hope it makes sense and thanks for your help!!
You can use array spread or Array.concat() to make a shallow clone, and add new items as well) so change the line below:
const newFriends = selectedFriends
to this line :
const newFriends = [...selectedFriends]
I have this FormControl element with Select that accepts an array of options that is being used for MenuItem options and also a value as props and this component looks like this:
const TaxonomySelector = (props) => {
const { isDisabled, taxonomies, selectedTaxonomy, handleTaxonomyChange } = props;
return (
<Grid item xs={12}>
{console.log(selectedTaxonomy)}
{console.log(taxonomies)}
<FormControl disabled={isDisabled} fullWidth>
<InputLabel>Таксономия</InputLabel>
<Select
value={selectedTaxonomy || ''}
onChange={handleTaxonomyChange}>
{Object.values(taxonomies).map((taxonomy) => (
<MenuItem key={taxonomy.id} name={taxonomy.name} value={taxonomy}>
{taxonomy.name} от {moment(taxonomy.date).format('YYYY-MM-DD')}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
);
};
The values that I pass as props are correctly displaying as filled out in the console at all stages the component is being rendered. And also in the case when this component is used for selection through using handleTaxonomyChange function everything is working correctly with user being able to select a particular option out of the array provided to the MenuItem. However, the problem occurs in case when the parent component of this component is being open for View Only or with already pre-defined values. In this case I get the following:
It seems like there's something is being passed to the Select component (even I checked through React Component in DevTools and value was showed correctly) but for some reason it is not being displayed.
The parent component contains the following code related to this part:
const INITIAL_SELECTED_TAXONOMY = null;
const [selectedTaxonomy, setSelectedTaxonomy] = useState(INITIAL_SELECTED_TAXONOMY);
const handleTaxonomyChange = (e) => setSelectedTaxonomy(e.target.value);
useEffect(() => {
getTaxonomies();
}, []);
useEffect(() => {
if (viewTypeOnlyView) {
handleStageChange(1);
handleDialogTitleChange('Конструктор КС. Режим просмотра');
}
if (viewTypeEdit) {
handleDialogTitleChange('Конструктор КС. Режим редактирования');
}
if (viewTypeCopy) {
handleDialogTitleChange('Конструктор КС. Дублирование КС');
}
if (defaultData) {
if (defaultData.name) setName(defaultData.name);
if (defaultData.taxonomy) setSelectedTaxonomy(defaultData.taxonomy);
// if (defaultData.entryPoints) setSelectedEntryPoints(defaultData.entryPoints);
if (defaultData.entryPoints) {
getEntryPointDescsFn('4.1', defaultData.entryPoints);
}
if (defaultData.message) setMessage(defaultData.message);
}
}, [viewType]);
ViewType is a prop that is being passed to this component and calling those methods in order to fill up the state with predefined values.
And the TaxonomySelector component inside the return statement:
<TaxonomySelector
taxonomies={taxonomies}
isDisabled={currentStage === 1}
selectedTaxonomy={selectedTaxonomy}
handleTaxonomyChange={handleTaxonomyChange} />
At first I thought that the issue could be related to how the component is being rendered and maybe it renders before that data pre-fill useEffect hook is being triggered. However, it seems that other elements, like the ones with string values name and message are being correctly filled out with no issues. Seems like that the issue is specifically related to Select elements. Could you let me know what could it possibly be?
Looks like disabled prop in FormControl is true.
For debug set disabled prop false
I tried Googling this but due to the English language not being my greatest skill I find it hard to Google the right thing. I've tried to fix this problem myself which works, but I wonder if there are better ways to do this.
Situation:
I have a json object and want to render the values in it, so I mapped the values and render it in a component. I only have to call that component once and it will render itself multiple times acoording to how many objects are in my json object.
Now when I fire the onPress function on a single, specific rendered instance, it will fire for every rendered instance.
Desired situation:
When I have multiple rendered instances of my component, and I fire onPress on a single one, it should only fire for that instance.
Code:
I'm performing a get request and response returns a json object:
{
"objects": [
{
"name": "Alarm chauffeurs#ON=100",
"object_id": 1,
"input_value": 0,
"last_changed": "2019-03-08T14:30:54",
}, ...
Next I map the values to my component:
let cards = this.state.dataSource.objects.map((val, key) => {
return (
<Animated.View key={key} style={[cardStyle.container, { height: this.state.collapse[parseInt(val.object_id)] }]}>
<TouchableHighlight onPress={() => this.toggle(val.object_id)} />
</Animated.View>
);
});
Now I have to call {cards} in my MainView only once and React Native will render multiple cards:
return (
<View style={style.container}>
<View style={style.colDash}>
<ScrollView>
{cards}
</ScrollView>
</View>
</View>
);
Here's an image of the output
Now once I press a button, onPress gets fired on every component on screen.
I've tried:
Created an array and pushed a random value to it as id always starts at 1. The rest is done with a simple for loop. This probably isn't the best way to handle my problem.
toggle(id) {
var test = []
test.push(123);
for (var i = 1; i < 8; i++) {
if (id == i) {
var temp = new Animated.Value(75);
Animated.timing(
temp, {
toValue: 150,
duration: 500,
}
).start()
test.push(temp);
}
else {
var temp = new Animated.Value(75);
Animated.timing(
temp, {
toValue: 75,
duration: 1,
}
).start()
test.push(temp);
}
}
this.setState({
collapse: test
});
Later on, I also want to add the possibility to be able to toggle multiple instances at the same time. As of now when I fire one onPress, the rest gets reset.
Add an index to your TouchableHighlight component and try then
I wanted to know if KeyExtrator contributes to maintain the unique list ?
if yes then its not helping me at all.
I am trying to display elements on flat list when a new Item comes in, the list just addes it without checking for uniqueness of the list
Main goal is to make my list unique, I had tried ListView It was working
due to logic implemented using rowHasChanged method.trying to see if there is anything for FlatList.
constructor(props) {
super(props);
this.state = {
TESTDATA: [{
BarCode: '1',
StopAddress: 'Label 1'
}]
};
};
onReceivedMessage(messages) {
var jsondata = JSON.parse(messages)
var dataTest = this.state.TESTDATA;
var Data = addressToDataMap.get(dataTest.BarCodes);
dataTest.push(jsondata);
this.setState({
TESTDATA: dataTest
});
}
<FlatList
extraData={this.state}
data={this.state.TESTDATA}
keyExtractor={(item, index) => item.BarCode.toString()}
renderItem={({ item}) => (
<Text style={styles.baseText}>
Stop Address: {item.StopAddress}{'\n'}
Barcodes: {item.BarCode}
</Text>
)}
/>
I wanted to know if KeyExtrator contributes to maintain the unique list ? NO is the Answer
Explanation : keyExtractor tells the list to use the ids for the react keys instead of the default key property. Keys help React identify which items have changed, are added, or are removed. Key also used for caching and as the react key to tracking item re-ordering. These unique keys are what allow the VirtualizedList (which is what FlatList is built on) to track items and are really important in terms of efficiency. The keyExtractor falls back to using the index by default. For example
<FlatList
style={{}}
data={this.state.FeedDataCollection}
keyExtractor={(item, index) => item.key}
renderItem={(rowData) =>this.RenderFeedCard(rowData)}
/>
Here item.key is is key.So if you want display elements based on the uniqueness, you have write a logic for that. keyExtractor cant do that. Learn more about React Keys here https://reactjs.org/docs/lists-and-keys.html and KeyExtractor here https://facebook.github.io/react-native/docs/flatlist#keyextractor
I have a list just simple text that rendering into flatlist on react native but I am experiencing very very slow performance which makes app unusable.
How can I solve this? My code is:
<FlatList
data={[{key: 'a'}, {key: 'b'} ... +400]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
Here is my suggestions:
A. Avoid anonymous arrow function on renderItem props.
Move out the renderItem function to the outside of render function, so it won't recreate itself each time render function called.
B. Try add initialNumToRender prop on your FlatList
It will define how many items will be rendered for the first time, it could save some resources with lot of data.
C. Define the key prop on your Item Component
Simply it will avoid re-render on dynamically added/removed items with defined key on each item. Make sure it is unique, don't use index as the key! You can also using keyExtractor as an alternative.
D. Optional optimization
Try use getItemLayout to skip measurement of dynamic content. Also there is some prop called maxToRenderPerBatch, windowSize that you can use to limit how many items you will rendered. Refer to the official doc to VirtualizedList or FlatList.
E. Talk is Cheap, show me the code!
// render item function, outside from class's `render()`
const renderItem = ({ item }) => (<Text key={item.key}>{item.key}</Text>);
// we set the height of item is fixed
const getItemLayout = (data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
);
const items = [{ key: 'a' }, { key: 'b'}, ...+400];
function render () => (
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={getItemLayout}
initialNumToRender={5}
maxToRenderPerBatch={10}
windowSize={10}
/>
);
Try out this listview https://github.com/Flipkart/ReactEssentials, it renders far fewer items than FlatList and then recycles them. Should be much faster.
npm install --save recyclerlistview
check this link
https://github.com/filipemerker/flatlist-performance-tips
Example
FlatList
containerContentStyle={styles.container}
data={countries}
renderItem={({ item }) => (
<View style={styles.results}>
<Results
{...this.props}
country={item}
handleUpdate={this.handleUpdate}
pendingCountry={pendingCountry}
/>
</View>
)}
keyExtractor={item => item.alpha2code}
ListHeaderComponent={() => this.renderHeader()}
// Performance settings
removeClippedSubviews={true} // Unmount components when outside of window
initialNumToRender={2} // Reduce initial render amount
maxToRenderPerBatch={1} // Reduce number in each render batch
updateCellsBatchingPeriod={100} // Increase time between renders
windowSize={7} // Reduce the window size
/>
One of the simple ways to optimize your flatlist is by using React.memo. In technical words, it basically does a shallow comparison of your data and check whether they needs to be re-rendered or not.
Make a file such as ListComponent.js and add the renderItem JSX to it, and and it to the renderItem.
// ListComponent.js
import React, { memo } from "react";
import { StyleSheet, Text, View } from "react-native";
const ListComponent = ({ item }) => {
return <View ></View>
};
export default memo(ListComponent);
Here is your FlatList
<FlatList
data={data}
removeClippedSubviews={true}
maxToRenderPerBatch={8}
windowSize={11}
initialNumToRender={8}
keyExtractor={keyExtractor}
renderItem={({ item }) => (
<ListComponent item={item} />
)}
/>
Another optimization would be to provide a key using keyExtractor prop. It's very important.
I used 'react-native-optimized-flatlist'
and my problem was solved, the only thing to be careful about is that when you use this package, it removes keyExtractor and extraData
You can use react-native-optimized-flatlist. It is the optimized version of Flatlist.
1) Add this package by :
npm i -S react-native-optimized-flatlist
OR
yarn add react-native-optimized-flatlist
2) Replace <FlatList/> by <OptimizedFlatlist/>