If items don't have the key attribute, rowMap is useless
michaelkowalski opened this issue · 11 comments
Hello, if the items in the list don't have the key
attrubute, rowMap
becomes one big merged row. I tried to use keyExtractor, but SwipeList seems to ignore it and still needs the key attr in the data prop.
The problem in my case is that I had to explicitly add the key
attribute to the data prop to use .closeRow()
.
Is this a bug or am I doing something wrong? For me the expected behavior would be to use any data shape, a proper keyExtractor and still have the rowMap
working correctly. Please correct me if I'm wrong.
+1
Hey @michaelkowalski @elioncho I'm trying to repro this but I'm just not seeing the same issues. I'm going to lay out everything I'm looking at and you tell me what I'm missing.
Here is my example code. I do not have key
s in my data array. Instead I'm using a keyExtractor
and using data.text
as the key since it is unique.
Note that the log in onRowDidOpen
is showing the correct key, and the rowMap which is an object where the keys are the keys from keyExtractor and the values are the SwipeRow
s. The row is closing on scroll and when I click "close".
Also note when I invoke closeRow
I'm passing the data.item.text
as the second arg since that is what I use as my unique keys.
class App extends Component {
constructor(props) {
super(props);
this.state = {
listViewData: Array(20)
.fill('')
.map((_, i) => ({ text: `item #${i}` })),
};
}
closeRow(rowMap, rowKey) {
if (rowMap[rowKey]) {
rowMap[rowKey].closeRow();
}
}
onRowDidOpen = (rowKey, rowMap) => {
console.log('Row key: ', rowKey);
console.log('Row map: ', rowMap);
};
render() {
return (
<View style={styles.container}>
<SwipeListView
data={this.state.listViewData}
keyExtractor={item => item.text}
renderItem={data => (
<TouchableHighlight
onPress={() => console.log('You touched me')}
style={styles.rowFront}
underlayColor={'#AAA'}
>
<View>
<Text>
I am {data.item.text} in a SwipeListView
</Text>
</View>
</TouchableHighlight>
)}
renderHiddenItem={(data, rowMap) => (
<View style={styles.rowBack}>
<Text>Left</Text>
<TouchableOpacity
style={[
styles.backRightBtn,
styles.backRightBtnLeft,
]}
onPress={() =>
this.closeRow(rowMap, data.item.text)
}
>
<Text style={styles.backTextWhite}>Close</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.backRightBtn,
styles.backRightBtnRight,
]}
>
I don't see anything wrong. This is what I'm personally doing.
I am trying to display an array of basic array of objects:
[{"id":20,"name":"Item 1"},{"id":2,"name":"item 2"},{"id":3,"name": "item 3"}]
<SwipeListView
keyExtractor={item => String(item.id)}
data={lists}
renderItem={data => (
<ListItem
title={data.item.name}
onPress={() => {
navigate('ListDetails', {
listId: data.item.id,
listName: data.item.name
})
}}
/>
)}
If I remove the keyExtractor prop and add the key property to every element of the array it works. I'm not sure what I'm doing wrong then.
Hey @elioncho I'm not seeing any closeRow
function here, what exactly is the issue you're trying to fix?
I copied the whole component and the closeRow and deleteRow functions in the following example. The only thing I'm not copying are the styles.
useEffect(() => {
Client.lists.index()
.then(result => {
result.forEach(item => item['key'] = item.id); // adding key to make it work
setIsLoading(false);
setLists(result);
})
}, [])
closeRow = (rowMap, rowKey) => {
console.log('rowKey', rowKey);
if (rowMap[rowKey]) {
rowMap[rowKey].closeRow();
}
}
deleteRow = (rowMap, rowKey) => {
closeRow(rowMap, rowKey);
const newList = [...lists];
const prevIndex = lists.findIndex(item => item.key === rowKey);
newList.splice(prevIndex, 1);
setLists(newList)
Client.lists.destroy(rowKey);
}
<SwipeListView
keyExtractor={item => String(item.id)}
data={lists}
renderItem={data => (
<ListItem
title={data.item.name}
onPress={() => {
navigate('ListDetails', {
listId: data.item.id,
listName: data.item.name
})
}}
bottomDivider
chevron
/>
)}
renderHiddenItem={({ item }, rowMap) => (
<View>
<TouchableOpacity
onPress={() => {
closeRow(rowMap, item.key);
navigate('EditList', { list: item })
}}
>
<Text>
Edit
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() =>
deleteRow(rowMap, item.key)
}
>
<Text>
Delete
</Text>
</TouchableOpacity>
</View>
)}
rightOpenValue={-150}
style={{ marginBottom: 60 }}
keyExtractor={(item) => String(item.id) }
ListEmptyComponent={
<EmptyComponent message="You haven't created any lists yet." />
}
/>
If I remove the line that sets the key attribute on the result array (see the useEffect method), I will get a:
rowKey undefined
on the console.log in the closeRow function.
Hey @elioncho thanks for the detailed example and sorry for the delay in responding.
So in your example if you don't have a key
set on every object in your data
than indeed item.key
will be undefined
as you pass it to your closeRow
function. In that case you'll have to pass the same "key" that your keyExtractor
function is creating for each object. So in your example you should change this line:
<TouchableOpacity
onPress={() => {
closeRow(rowMap, item.key);
to this:
<TouchableOpacity
onPress={() => {
closeRow(rowMap, String(item.id));
And all should work as intended :) let me know if that's not the case, thanks!
Works perfectly, thanks!
Going to close this, please reopen if anyone else has issues, thanks
Jessie @jemise111 I'm wondering, why does the key
(see the item.key
I quote here) need to be a String, presently?
My keys are all integers, everywhere. Coming at this straight and clean, no legacy to worry about, this machination is a source of cognitive loading, and code complexity. So I'm fascinated, why a string, strictly?
<TouchableOpacity
onPress={() => {
closeRow(rowMap, item.key);
Hey @StevenBlack in this case above I was simply using the key provided by the OP's keyExtractor
function above. i.e.
keyExtractor={item => String(item.id)}
You could, in theory, use integers for your keys. This component will function properly. However you will get a warning from react-native. See this discussion for more:
ah! So ignore the warning. Cool! That's valuable input. Thank you, Jessie @jemise111.