A drop-in replacement for React Native's ListView
.
✨ NEW! It also supports the new VirtualizedList
component (the underlying component used by FlatList
) as of v0.3.0
;
see the instructions at the bottom for more details and version requirements.
It supports Immutable data out-of-the-box to give you faster performance and less headaches.
- Do you find yourself re-implementing
rowHasChanged
and settingdataSource
over and over? - Do you use Immutable data, only to write wrappers for data access in order to use them with a ListView?
- Do you listen for lifecycle events simply so you can update
dataSource
-- and thus you can't easily use pure functional components with lists? - Do you have nested objects in your state so a shallow diff won't cut it for pure rendering?
- Do you show 'Loading...', 'Empty', and 'Error!' states in your lists?
- Do you use a navigator and want better performance while animating?
If you answered yes to ANY of these questions, this project will surely help. Check out the examples below!
<ImmutableListView
immutableData={this.state.listData}
renderRow={this.renderRow}
/>
The screenshot above shows two different lists. The first simply uses this data:
Immutable.fromJS({
'Section A': [
'foo',
'bar',
],
'Section B': [
'fizz',
'buzz',
],
})
The second list is even simpler:
Immutable.Range(1, 100)
It supports all the props of React Native's ListView
,
but instead of passing in a dataSource
, you pass in a prop called immutableData
.
This prop is just the raw data you'd like to display -- ImmutableListView
will handle creating an efficient dataSource
for you.
Other than this small change, everything else will be exactly the same as ListView
.
There's an example app here if you'd like to see it in action.
-
Install:
-
Import it in your JS:
import { ImmutableListView } from 'react-native-immutable-list-view';
You can remove all that boilerplate in your constructor, as well as lifecycle methods like
componentWillReceiveProps
if all they're doing is updating your dataSource
.
ImmutableListView
will handle all of this for you.
Check out this example diff:
Note: This looks much better on GitHub than on npm's site. Red means delete, green means add.
-import { Text, View, ListView } from 'react-native';
+import { Text, View } from 'react-native';
+import { ImmutableListView } from 'react-native-immutable-list-view';
import style from './styles';
import listData from './listData';
class App extends Component {
- constructor(props) {
- super(props);
-
- const dataSource = new ListView.DataSource({
- rowHasChanged: (r1, r2) => r1 !== r2,
- sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
- });
-
- const mutableData = listData.toJS();
-
- this.state = {
- dataSource: dataSource.cloneWithRowsAndSections(mutableData),
- };
- }
-
- componentWillReceiveProps(newProps) {
- this.setState({
- dataSource: this.state.dataSource.cloneWithRows(newProps.listData),
- });
- }
-
renderRow(rowData) {
return <Text style={style.row}>{rowData}</Text>;
}
renderSectionHeader(sectionData, category) {
return <Text style={style.header}>{category}</Text>;
}
render() {
return (
<View style={style.container}>
<Text style={style.welcome}>
Welcome to React Native!
</Text>
- <ListView
- dataSource={this.state.dataSource}
+ <ImmutableListView
+ immutableData={listData}
renderRow={this.renderRow}
renderSectionHeader={this.renderSectionHeader}
/>
</View>
);
}
}
All the props supported by React Native's ListView
are simply passed through, and should work exactly the same.
You can read about them here.
You can fully customize the look of your list by implementing renderRow
and, optionally, renderSectionHeader
.
Here are the additional props that ImmutableListView
accepts:
Prop name | Data type | Default value? | Description |
---|---|---|---|
immutableData |
Any Immutable.Iterable |
Required. | The data to render. See below for some examples. |
rowsDuringInteraction |
number |
undefined |
How many rows of data to initially display while waiting for interactions to finish (e.g. Navigation animations). |
sectionHeaderHasChanged |
func |
(prevSectionData, nextSectionData) => false |
Only needed if your section header is dependent on your row data (uncommon; see ListViewDataSource 's constructor for details). |
renderEmpty |
string or func |
undefined |
If your data is empty (e.g. null , [] , {} ) and this prop is defined, then this will be rendered instead. Pull-refresh and scrolling functionality will be lost. |
renderEmptyInList |
string or func |
'No data.' |
If your data is empty (e.g. null , [] , {} ) and this prop is defined, then this will be rendered instead (inside of an EmptyListView ). Pull-refresh and scrolling functionality will be kept! |
Methods such as scrollTo
and scrollToEnd
are passed through just like the props described above.
You can read about them here.
The references to ListView
and VirtualizedList
are available via getListView()
and getVirtualizedList()
.
These references allow you to access any other methods on the List component that you might need.
ImmutableListView
accepts several standard formats
for list data. Here are some examples:
[rowData1, rowData2, ...]
{
section1: [
rowData1,
rowData2,
...
],
...
}
{
section1: {
rowId1: rowData1,
rowId2: rowData2,
...
},
...
}
To try it out yourself, you can use the example app!
When using section headers, ImmutableListView
treats certain types of Immutable.Map
slightly differently
than ListView
treats an equivalent plain JS Map
. See the snapshot test output
here
for an example of how ImmutableListView
behaves, or try it for yourself.
It seems based on the current documentation
that ImmutableListView
is behaving as expected, and in fact regular ListView
is the one being weird.
In any case, you should make sure to test this behavior yourself if you're using a Map
with section headers.
Other than this, the two should behave identically. You can verify this with the unit tests here.
This component takes an optional emptyText
prop and renders an ImmutableListView
with only a single list item with the text you specified.
By default, this string is simply 'No data.'
.
Example:
import { ImmutableListView, EmptyListView } from 'react-native-immutable-list-view';
<ImmutableListView
immutableData={this.state.listData}
renderRow={this.renderRow}
renderEmpty={() => <EmptyListView emptyText="Nothing to see here!" />}
/>
If you need more flexibility, instead of passing emptyText
to EmptyListView
, you can pass renderRow
and display anything you want.
EmptyListView
will pass all your props through to ImmutableListView
(and then through to ListView
).
If you want to handle something like pull-refresh while empty, you can use the originalProps
, e.g.:
<ImmutableListView
...
refreshControl={<RefreshControl ... />}
renderEmpty={(originalProps) =>
<EmptyListView
refreshControl={originalProps.refreshControl}
emptyText="Nothing to see here!"
/>
}
/>
Just as the ImmutableListView
component helps render a ListView
using Immutable data,
ImmutableVirtualizedList
helps render the new and improved VirtualizedList
component.
This is the underlying component that FlatList
uses.
There's a Medium article about it if you'd like more context. The short version of the setup instructions is below:
-
Make sure you're using at least
v0.5
of this library -
Import the component:
import { ImmutableVirtualizedList } from 'react-native-immutable-list-view';
-
Make sure you're using
v0.4.x
of this library -
Download the required files into your app's
node_modules
(since these components aren't published in a release quite yet):https://gist.github.com/cooperka/c5dd3ab11f588044d4d6ba22d52c4ab0
or, using the shell script from this library:
npm run download-flatlist
-
Import the component directly:
import ImmutableVirtualizedList from 'react-native-immutable-list-view/lib/ImmutableVirtualizedList';
After following the above steps, simply render it:
<ImmutableVirtualizedList
immutableData={this.state.listData}
renderItem={this.renderItem}
/>
See the example app for a working demo,
or React Native's FlatListExample
for an idea of what features are possible.