背景
Mobx で管理されたローカルなステートを React Native の FlatList に渡す時に、下記のWarningが出て困った
[mobx.array]
Attempt to read an array index(2) that is out of bounds (2).
Please check length first. Out of bound indices will not be tracked by MobX
この原因は、MobXの @observable
デコレーターで定義された配列は、JSの Array
ではなく、 MobXの ObservableArray
になるため、 ListView
, FlatList
, SectionList
などが解釈できない形になっているらしい。
なので、 ObservableArray
の .toJS
あるいは .slice()
メソッドでJSの Array
に変換してあげれば良い。
// @flow
import React from 'react'
import { observable } from 'mobx'
import { FlatList, Text } from 'react-native'
import { userApi } from 'app/api'
type User = {
id: number,
name: string
}
class SomeComponent extends React.Component {
@observable users: Array<User>
componentDidMount() {
userApi.get().then(users => { this.users = users })
}
render() {
<FlatList
keyExtractor={(user) => String(user.id)}
renderItem={({ item }) => <Text>{item.name}</Text> }
// ここで data={this.users} とするとエラーを吐く
data={this.users.slice()}
/>
}
}
追記
Warningが出ない場合でも、再描画されない場合があるので、 .slice()
した方が良さげ。
特に、空の配列に要素を追加した時や、オブジェクトの配列でそのオブジェクトの一部を変更した、みたいな場合に再描画されない。
.slice()
しなくて良い場合もある。逆に常に .slice()
していると、パフォーマンスに影響がありそう。
個人的には、リアクティブに再描画されない問題が起こると原因追求が難しいので、多少のパフォーマンスを犠牲にしても .slice()
してしまっている。
Storeのユニットテストだけは書いておいた。
どんな場合に再描画されないのか、ちゃんと調べたい。
参考