re-render flatlist when data changes in react-native - react-native

I have a flatlist in a react-native app. This flatlist is filled with an array (renderItem). This works.
When I click on an item in the flatlist, the item should dissapear.
const [currentValues, setCurrentValues] = useState([])
// a fetch fills the currentValues-array with data from a
// database here, this works
<FlatList
data={currentValues}
renderItem={(item, index) => {
return (
<TouchableOpacity onPress={() => deleteItem(item.id)} style={{ borderWidth: 1, borderColor: 'black' }}>
<Text>{item.username}</Text>
</TouchableOpacity>
)
}} />
With the following function, I delete the item from the array:
const deleteItem = id => {
let array = currentValues
let index = array.findIndex(e => e.id === id)
array.splice(index, 1)
setCurrentValues(array)
}
The following problem occurs:
The deletion of the item in the array works, because when I do a console.log, I see the array is empty. The FlatList however does not re-render. When I click on an item to delete it, the username disappears (because it's deleted from the currentValues-item), but the item itself is not deleted, I still see a bordered rectangle.
When I just reset the array after I click one item, it works, so then the FlatList is re-rendered like it should. But then, all items are deleted at once (because I clear the array), and that's not what I want.
I tried the extraData prop. I added this to my flatlist:
extraData={currentValues}
I want to make sure the FlatList re-renders when the currentValues-array is updated, but this also does not work.
Does anyone knows how to solve this?
BTW, changing from FlatList to ScrollView works (with the exact same functions). So I think it's a FlatList issue.
Solution
The solution (based on MBach's answer):
const deleteItem = id => {
let array = [...currentValues]
let index = array.findIndex(e => e.id === id)
array.splice(index, 1)
setCurrentValues([...array])
}

Try to force clone your array with spread operator:
let array = [...currentValues]
Or you can connect currentValues to useEffect()

Related

React Native flatlist inverted

I have a flatlist that shows a messages chat. I want to start at the bottom of the flatlist (showing the most recent messages). I added 'inverted={true}' and 'data={data.reverse()}' as the props.
It is sort of working, but the data alternates being reversed and not reversed every time i leave and enter the chat again.
Anyone know the fix?
Attached code
.reverse() mutates the original list.
If you do want your list reversed, do it like this
const App = ({ data }) => {
const reversed = [...data].reverse(); // Make a shallow copy, then reverse the copy
return <Flatlist data={reversed} inverted />;
};
However, you will probably notice that you don't want your list reversed, because inverted is already reversed.
const App = ({ data }) => {
return <Flatlist data={data} inverted />;
};

React Native doesn't re-render on DOM change

I had an array of components inside a ScrollView component. Somehow react native doesn't re-render when the array is modified.
Here's a demonstration of my problem:
const TestApp = () => {
const [arr, setArr] = useState([]);
function pushArr() {
setArr((arr) => {
arr.push(1);
return arr;
});
console.log('pushArr():', arr);
}
function flushArr() {
setArr([]);
console.log('flushArr():', arr);
}
useEffect(() => {
console.log('useEffect():' , arr);
})
return (
<>
<ScrollView style={{flex:1}}>
{arr.map((elem, i) => <Text key={i}>{elem}</Text>)}
</ScrollView>
<Button title="Push" onPress={pushArr}></Button>
<Button title="Flush" onPress={flushArr}></Button>
</>
)
}
The page remains blank, and no updates happen on button press.
I've logged out arr and these are my findings:
pushArr() and flushArr() works as expected
useEffect() gets triggered only on startup and after flushArr()
Can anyone explain this behavior, and what mistakes have I made?
If I remember correctly, you need to make a copy of the array whenever you want it to “react”. The new memory address will let react know it should update. In other words, you shouldn’t mutate the array.
You can use the spread operator to make a copy and then push an element to the end which you can then pass to useArr. Usually I see people just passing the new object inside your useArr function.
I also don’t see you passing anything to your useArr function.

react-naitve, how to move to certain item in flatlist

I am making my own app using react-native
I have some difficulties to do notice function.
We have board and comments, if we get notice that someone writes comment, I want to go to that comments in flatlist comments (there can be many comments in board, so if we just go to board without focusing notice comments, user may be hard to find comments). I searched some example of scrollindex method in flatlist. But I want to go directly without button or input. We have comment id in notice model. So first I think that if we compare comment id in flatlist and set state, we can save index of that comments. But setState() doesn't work in flatlist because of error "cannot update a component from inside the function body of a different component."
And second, i don't know where we put the function scrollToIndex() because it may move after rendering is finished. But i don't know that state.
IF you know how to solve that problem pleases help me!!
(you can think instagram comment notice, if we click them , we can see that comment, no matter where they were, becauses it automatically move to that comments
getItemLayout = (data, index) => (
{ length: 50, offset: 50 * index, index }
)
getColor(index) {
const mod = index%3;
return COLORS[mod];
}
scrollToIndex = () => {
let randomIndex = Math.floor(Math.random(Date.now()) * this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index: randomIndex});
}
<FlatList
style={{ flex: 1 }}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={item => item}
getItemLayout={this.getItemLayout}
initialScrollIndex={50}
initialNumToRender={2}
renderItem={({ item, index}) => (
<View style={{...style, backgroundColor: this.getColor(index)}}>
<Text>{item}</Text>
</View>
)}
{...this.props}
/>
It is is simple code that use scrolltoindex. It can move to random index item
But I want to move certain item doing switch screen. In noticescreen, if we click notice , we need to move board that have that comments. Then we scroll to the comments to show user. We have board id and comments id in notice model. It is not hard to move to the board. But scroll to the comments is hard to me. Any solution?? please

getItemLayout in FlatList passing index -1 on first render

I am implementing a FlatList with initialScrollIndex and getItemLayout. However, every time my app starts up, it renders the first elements somehow and then jumps to the actual initialScrollIndex. This means that the actual performance boost that I am supposed to get, isn't working.
When checking the getItemLayout function I can see that when it renders the first index that is passed is -1 instead of the initialScrollIndex, thus throwing an error and breaking. The other random indexes are passed untilinitialScrollIndex` is passed.
Any ideas why this might be happening?
FlatList:
renderMonthPerMonth() {
const data = this.deriveMonthPerMonthDataFromProps();
const initialScrollIndex = this.deriveInitialScrollIndex();
return (
<FlatList
data={data}
ref={'flatlist'}
initialNumToRender={3}
onLayout={this.onLayout}
getItemLayout={this.getItemLayout}
showsVerticalScrollIndicator={false}
initialScrollIndex={initialScrollIndex}
renderItem={this.renderOneMonthPerMonth}
ListHeaderComponent={this.renderListHeader}
keyExtractor={el => `${el.monthName}-monthPerMonth`}
onScrollBeginDrag={() => this.onExpandMenu('scroll')}
ItemSeparatorComponent={this.renderItemSeparator}
ListFooterComponent={this.renderFooterComponent}
/>
);
}
getItemLayout:
getItemLayout(data, index) {
const monthAsNumber = moment().month(data[index].monthName).format('M')-1;
const SEPARATOR_HEIGHT = 25;
const MONTH_NAME_CONTAINER_HEIGHT = 55;
const ONE_DAY_HEIGHT = (((width-40)/7)/1.1);
const WEEKS_IN_MONTH = this.weeksInMonth(moment().format('YYYY'), monthAsNumber);
// define oneMonthHeight using weeksInMonth method
const oneMonthHeight = (ONE_DAY_HEIGHT * WEEKS_IN_MONTH) + (MONTH_NAME_CONTAINER_HEIGHT + SEPARATOR_HEIGHT);
return {
length: oneMonthHeight,
offset: oneMonthHeight * index,
index,
}
}
Console:
I have a suspicion the problem is in the renderItem prop and subsequently the renderOneMonthPerMonth() method.
Referring to the FlatList docs:
This is a PureComponent which means that it will not re-render if props remain shallow-equal. Make sure that everything your renderItem function depends on is passed as a prop (e.g. extraData) that is not === after updates, otherwise your UI may not update on changes. This includes the data prop and parent component state.
and
renderItem({ item: Object, index: number, separators: { highlight: Function, unhighlight: Function, updateProps: Function(select: string, newProps: Object) } }) => ?React.Element
Having this.deriveMonthPerMonthDataFromProps() may not be Pure (depending on that prop, I cannot see it).
Try fetching all the data and assigning to a const outside the renderItem method that is passed. Alternatively if this data is pure and not changing you can skip this step.
The next two are probably the crux of the issue: create a render function resembling this:
const renderItem = ({ item, index }) => {
return <DummyComponent item={item} index={index} /> // item and index are not necessarry just demonstrating
}
Ensure you have a keyExtractor returning unique identifier. Else this will error in a similar manner to your issue. Try the below as a sanity check
keyExtractor={(item, index) => index.toString()}
If the above is not the issue you also need to ensure that your data prop on your Flatlist is an array and not an Object or other data structure as explained in the data section of the Flatlist docs.
If your data is of type object and you don't rely on the keys to access specific pieces of data in other methods, you can change the initial data to be an array of objects like so:
[
{ ...monthOneData },
{ ...monthTwoData },
{ ...monthThreeData }
]
Or, if you want to keep the original data as an object you can convert it to an array of keys by using
Object.keys(this.state.data)
Then in your FlatList you could do something like this:
<FlatList
data={Object.keys(this.state.data)}
renderItem={({item}) =>
<DummyComponent month={this.state.data[item].month} /> // item is now the key value of the objects (almost like a look up table)
// also not having curly braces implies an implicit return in this case
}
/>

What is renderRow ()

I went through React-Native docs to figure out what is renderRow() but for some reason I am unable to comprehend what does it say from Facebook React-Native docs
This what the official docs says
renderRow
(rowData, sectionID, rowID, highlightRow) => renderable
Takes a data entry from the data source and its ids and should return a renderable component to be rendered as the row. By default the data is exactly what was put into the data source, but it's also possible to provide custom extractors. ListView can be notified when a row is being highlighted by calling highlightRow(sectionID, rowID). This sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you to control the separators above and below the highlighted row. The highlighted state of a row can be reset by calling highlightRow(null).
[Question:] Can someone please explain me this with example?
ListView is deprecated, use FlatList instead with the equivalent renderItem method. This is responsible of the actual rendering of each row based on the data records:
const data = [
{ key: '1', label: 'foo' },
{ key: '2', label: 'bar' }
]
renderTheItem = ({item}) => {
return <Text>{item.label}</Text>
}
<FlatList
data={data}
renderItem={this.renderTheItem}
/>
And the rendered result will be something like this:
<View> --> coming from FlatList wrapper
<Text key="1">foo</Text> --> coming from the custom renderTheItem function
<Text key="2">bar</Text>
</View>
It is mandatory to either add a unique key prop for each data record, or define a keyExtractor function. Also important to destruct the item in the renderer function with ({item}) as it has other meta parameters as written in documentation of FlatList.
renderItem({ item, index, separators}) => {}