Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ListView] prepend function ? #1682

Closed
jawadrehman opened this issue Jun 19, 2015 · 21 comments
Closed

[ListView] prepend function ? #1682

jawadrehman opened this issue Jun 19, 2015 · 21 comments
Labels
Good first issue Interested in collaborating? Take a stab at fixing one of these issues. Help Wanted :octocat: Issues ideal for external contributors. Resolution: Locked This issue was locked by the bot.

Comments

@jawadrehman
Copy link

I would like to append stuff on a ListView, and stay on the original position

My rowHasChanged is r1.message != r2.message

Currently I unshift the new data to be appended. I have noticed sometimes it doesnt update the ListView, and I have had to tinker with the dirtyRows to force an update.

Can anyone help me achieve what I intend to ?

@brentvatne brentvatne changed the title ListView append function ? [ListView] append function ? Jun 19, 2015
@brentvatne
Copy link
Collaborator

Can you post the code that you're using with this? Are you using cloneWithRows?

@jawadrehman
Copy link
Author

My bad, I should have said prepend instead of append.

since its something to do with the history of messages, I first remove an item if it exists and then unshift it so that its at the top. Here's a small snippet

var chatMessages = main.state.chatMessages;
var id = messageKeys[jid];
chatMessages.splice(id, 1);
chatMessages.unshift(item);

//this is the code I am not too fond of, and would like it to be simpler
for(var i = 0, j = id; i<= j ; i++){
messageKeys[chatMessages[i].jid_id] = i ;
main.refs.ListMessageHistory.refs.listMessages.props.dataSource._dirtyRows[0][i] = true;
}
main.setState({chatMessages : chatMessages, messageKeys : messageKeys }, function(){
main.refs.ListMessageHistory._renderRows();
});

INSIDE MY _RENDERROWS function

this.setState({dataSource: this.state.dataSource.cloneWithRows(
this.props.chatMessages
)});

@jawadrehman jawadrehman changed the title [ListView] append function ? [ListView] prepend function ? Jun 21, 2015
@marcshilling
Copy link

I'm having a similar issue. Trying to dynamically add items the beginning of my ListView, but when I call setState to update the DataSource, it doesn't work correctly. I do get a new row added (to the end), but it's just a duplicate of the last item.

Before insertion:
[A]
[B]
[C]

Then, after inserting [D] at the beginning of my DataSource array I get:
[A]
[B]
[C]
[C]

If I explicitly return true from rowHasChanged, I get the expected result:
[D]
[A]
[B]
[C]

@akabab
Copy link

akabab commented Aug 4, 2015

had the same issue:
http://stackoverflow.com/questions/31774399/react-native-listview-how-to-push-new-rows-from-top
got to make it work with passing a new array reference (concat instead of unshift) to cloneWithRows,
but then it seems to rerender the whole list.. performance issues on large list.

@tachim
Copy link

tachim commented Aug 12, 2015

I'm seeing the same issue, but @akabab 's concat approach doesn't work in my case.

@JonathanWi
Copy link

Any news about this? I've spent the best part of my day trying to simply unshift a new item at the top of my ListView without success. I'm having the exact same issue than @marcshilling ...

PS: @marcshilling , can you explain what you mean by "If I explicitly return true from rowHasChanged"?

@marcshilling
Copy link

@JonathanWi

When defining the ListView dataSource, literally just doing this:

dataSource: new ListView.DataSource({
  rowHasChanged: (row1, row2) => {
    //return row1 !== row2;
    return true;
  }
})

@jasonmerino
Copy link

I just ran across this same issue and switching from unshift to concat worked for me.

@rt2zz
Copy link
Contributor

rt2zz commented Oct 22, 2015

After playing around with this a bit, it seems that because ListView assigns keys based on row index, anytime the datasource is prepended to, it will re-render all rows. This can be fixed by assigning a row specific key to the StaticRenderer on line 346 of ListView and also setting shouldUpdate={false}.

Obviously this is not a generic solution for react-native core, but it is trivial to copy your own "StaticListView" with these two line changes to prevent row rerender. Also I do not yet have it working with sticky headers.

Perhaps I am missing something, but it appears ListView's were not designed to prepend without rerender. It would be great to get this supported, but it would require a signifigant change to the way the data source works.

@rt2zz
Copy link
Contributor

rt2zz commented Oct 22, 2015

Actually sticky headers work no problem: https://gist.github.com/rt2zz/d1298e7b860e0707d7b4

Note for anyone trying to use this component, it is not well though out, and may break under certain prop combination or with unorthodox datasource setups. Also the row data must have a unique id property, as this is used as the row component key.

@brentvatne brentvatne added Good first issue Interested in collaborating? Take a stab at fixing one of these issues. Help Wanted :octocat: Issues ideal for external contributors. labels Oct 25, 2015
@marcshilling
Copy link

Ok so, I think I figured out what's going on with my particular issue.

The key was closing my data first so you get a brand new object reference, and then making my changes. I'm using the lodash library to accomplish this with the function _.cloneDeep() (https://lodash.com/docs#cloneDeep), like so:

let newData = _.cloneDeep(this.state.data);
newData.unshift({name: 'Marc Shilling', avatarUrl: null});
this.setState({
  data: newData
});

My new row is now successfully being added to the top of the ListView. Maybe you guys already knew this, but hopefully I'll help someone else. Still doesn't solve the issue of causing the entire list to be re-rendered though, since with the addition of the new row obviously everything is shifted by 1.

@JonathanWi
Copy link

Any news on this? Is there a way to prepend a row without re-rendering the whole list? This seems like a really buggy behavior

@ide
Copy link
Contributor

ide commented Nov 19, 2015

Here are the requirements for prepending without an entire re-render:

  • Write an efficient rowHasChanged function for your data source.
  • Part of making it efficient is that you might want to do a reference equality check
  • For a references equality check to be correct, do not mutate your row objects so you don't undermine the equality check
  • Use immutable objects if you aren't sure
  • Use the key prop on your rows. The keys should be based on the ids of your row objects. Do not use row indices as keys when you want to prepend because you'll introduce a massive re-render (think about how reconcilation works with React in general -- this isn't specific to React Native).

@ide ide closed this as completed Nov 19, 2015
@rops
Copy link

rops commented Mar 18, 2016

@brentvatne @ide by looking at ListViewDataSource, it looks like datasource assigns keys based on row index

newSource.rowIdentities = [];
newSource.sectionIdentities.forEach((sectionID) => {
   newSource.rowIdentities.push(Object.keys(dataBlob[sectionID]));
});

meaning that any time an item gets pretended a whole re-rerender is triggered. The key prop in this case is not relevant, is it?

Edit
Just noticed ListViewDataSource also support a getRowData constructor parameter..so I guess you can actually specify yourself which key to be used.

@lifuzu
Copy link

lifuzu commented Apr 13, 2016

According to the best answer here:
http://stackoverflow.com/questions/29351259/how-to-add-delete-item-into-listview

Just need 2 steps:

  1. Update/prepend the rows;
  2. Just call cloneWithRows.

"it tries to compare the new data rows with the existing rows (if any) in the dataSource, and figures out whether there are new rows to insert, or existing rows that need to be replaced or removed."

@duranmla
Copy link

What is wrong with the answer of @marcshilling ? seems to be exactly what we need to prepend data

@kyle-ssg
Copy link

kyle-ssg commented Feb 19, 2017

I thought I'd comment on this as it's confused me quite a few times in the past.. Using cloneDeep to achieve this is not really a good solution, it's quite an expensive and unnecessary operation if you have an array of nested objects and breaks the reference equality check ide mentioned. This basically makes rowHasChanged equate to true but is actually even less performant than doing rowHasChanged: (row1, row2) =>false.

I've been playing around with this myself, at first I was under the impression that using a combination of unshift and specifying rowIdentifiers would work e.g.
ds.cloneWithRows(newRows, newRows.map((row,i)=>i).reverse()) . However this didn't do the trick, the best solution I've found is to :

  • create a new array of rows using [newRow].concat(rows)
  • always use the key prop in render row (this prevents unnecessary renders) e.g. renderRow={(row)=>(<Text key={row.id}>{row.id}</Text>)}

I've recorded a quick example

@HootieBrown
Copy link

@kyle-ssg could you share more info about your config, I'm trying to prepend to the data source and your key fix isn't working for me

@kyle-ssg
Copy link

kyle-ssg commented Mar 6, 2017

See Here. This example highlights 2 cases, 1 where you use keys and another where you don't. You can take this example and pass <Example useKey={true|false}/>

@HootieBrown
Copy link

Thanks! Was this written recently? I'm on react-native 0.40.0 and its not working for me

@kyle-ssg
Copy link

kyle-ssg commented Mar 6, 2017

Example is using "react-native": "0.40.0", you can just run the app on the repo.

@facebook facebook locked as resolved and limited conversation to collaborators Jul 22, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 22, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Good first issue Interested in collaborating? Take a stab at fixing one of these issues. Help Wanted :octocat: Issues ideal for external contributors. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests