reactjs
reactnative
mobx

Mobx で管理されたステートを FlatList に渡す時は .slice() する

背景

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() した方が良さげ。
特に、空の配列に要素を追加した時や、オブジェクトの配列でそのオブジェクトの一部を変更した、みたいな場合に再描画されない。

https://stackoverflow.com/questions/44278526/react-native-flatlist-not-rerendering-row-when-props-change

.slice() しなくて良い場合もある。逆に常に .slice() していると、パフォーマンスに影響がありそう。
個人的には、リアクティブに再描画されない問題が起こると原因追求が難しいので、多少のパフォーマンスを犠牲にしても .slice() してしまっている。
Storeのユニットテストだけは書いておいた。

どんな場合に再描画されないのか、ちゃんと調べたい。

参考