mobx: [ReactNative] ListView.DataSource doesn't work with ObservableArrays
Hi,
I’m currently evaluating MobX to be used with React Native. It seems that ListView.DataSource unfortunately doesn’t work natively with observable arrays from MobX. I have to create a native array via toJS() in order the get the ListView show any items.
@observer
export default class SampleList extends Component {
render() {
const {store} = this.props;
const dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1.id !== r2.id
});
const items = toJS(store.items); // <= conversion to native array necessary
return (
<ListView
dataSource={dataSource.cloneWithRows(items)}
renderRow={data => (...)}
/>
);
}
}
I’ve just started experimenting with MobX but I’m a little concerned that calling toJS() for large collections on every render could lead to performance problems.
Please correct me if I’m wrong and there’s another way of getting the DataSource to accept ObservableArrays.
I understand that observable types are a consequence of MobX and that you cannot ensure that every library works out of the box with those types. However in ReactNative ListView is such a fundamental component that I hope there’s a decent solution when using MobX.
Thanks.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 1
- Comments: 34 (5 by maintainers)
@danieldunderfelt finally it worked! thanks. i am sure someone will encounter the same problem in the future since this is a common use case, so i will post the full working code.
Hi @winterbe!
I’ve been using mobx with RN extensively, and I haven’t gotten mobx “arrays” to work with datasources either. The reason might be that mobx arrays are really objects and datasource expects an array. I always call
sliceon the array before feeding it to the datasource.One trick is to create the datasource in a
computed, so the computed observes the reactive array and returns a datasource when accessed. Then just have theListViewuse the computed prop as its datasource.Datasource did not work with
peekeither if I recall correctly. This indicates that RN is doing something with the array, more than just reading from it. This could be interesting to research, as amobx-react-native-datasourcewould be a great module to have.@feroult The reason you’re not seeing updates is that
renderRowdoes not react to changes.observeronly makes the render function re-run on changes, not any other function. You need to make sure that your row component state is used within arenderfunction, and the easiest way to do that is to create a separate component.Then your
renderRowis simply:So it is not the datasource that is your problem at all, it is your render function.
It seems that the solution above doesn’t work for inner object property updates. For instance, if we have this observable, instead of a plain string array:
Then our
renderRowmethod should berenderRow={row => <Text>{row.text}</Text>}.Now if an
@actionupdate an item, likelist[0].text = 'new text', the ListView won’t update.The renderRow function won’t fire the reactions to recompute the
dataSource()method. I think this is right, because inside thedataSource()we don’t touch the inner object properties.The following hack will fire the ListView update after the item changes, but it doesn’t feel right 😃
Finally, this issue tells that we need to clone and update the object in the array instead of just changing it’s properties. Which doesn’t feel right too.
Since the hack works (although it re-renders all items in the ListView), it seems that it is possible to handle it properly with mobx, right?
Does anyone have a better idea on how to observe and fire the updates by using the inner property access that happens inside the
renderRowmethod call?@mweststrate I don’t know if it reacts to changes because the ListView doesn’t render any rows at all when calling
dataSource.cloneWithRows(items)with an ObservableArray instead of a native array. But callingsliceseems to be a decent workaround, thanks for that!@danieldunderfelt Using
computedfor the datasources is a great advice, thanks for that! I’m glad to hear people are already using MobX with ReactNative. I’m still evaluating if MobX could be a decent replacement for Redux in my app. ListView gave me a little trouble because of all the render* props which sometimes don’t react to state changes. I guess I haven’t understood entirely howobserveractually works. 😕If I remember correctly (not an active RN dev myself) the ListViewDataSource itself can work with observable arrays, but you need to make sure that your
renderRowis an observer component. (It looks like being part of theSampleList, but actually these component callbacks have their own lifecycle. SoShould do the trick. Let me know if it doesn’t 😃
@danieldunderfelt @feroult @binchik Thank you so much for all your help guys.
The issue was the nested component
MovieItemnot having the@observer, once added it worked!@MovingGifts I had the same problem. I ended up importing
Observercomponent frommobx-react. Then modify your renderRow code to look like this:renderRow = () => ( <Observer> {() => <MyRow />} </Observer> );Now the rows are rerendered as observable data changes.
I ran into a problem when I had a list with section headers. After digging through some code, it turns out that ListViewDataSource when it was calculating rowIdentities on an ObservableArray, it does Objects.keys on it. RN expects that it would output the indexes of the array, but it doesn’t because it’s an Observable Array. My solution here is when I call cloneWithRowsAndSections, I have to pass in the sectionIdentities and rowIdenties myself
Does it not react to changes in the row data, or to appending / remove items to the collection? For the latter you might need
items.slice()to make sure RN recognizes as array (difference withtoJSis that the first makes a cheap shallow copy, and the latter a deep copy, which shouldn’t be needed as individual rowData objects are observed byMyRow).cc: @danieldunderfelt