React Nativeで、いいねした時のタイムラグをなくす/componentWillReceivePropsをやめてgetDerivedStateFromPropsを使う

# 目的
ツイッターぽいいいねを実装する。
ツイッターていいね押すとすぐハートが赤くなるけど、(少し昔の)airbnbは赤くなるまで少し時間がかかる。
どちらが良いかという話ではなく、自分はツイッターみたくすぐ赤くしたかった。
これを実現するのが目的。

もう一つの話として、
reactのライフサイクルが新しくなる話。
https://mae.chab.in/archives/60040


今まで　```componentWillReceiveProps```　をダメと知りながらも愛用してきたが、やっぱり文句言われすぎて、手放すことにした。

上の記事によると、
```componentWillMount```、```componentWillReceiveProps```、 ```componentWillUpdate```
と合わせて、api通信時に色々問題を起こす可能性があるかつ、使い方が明確でなく、混乱するユーザーが多くいたらしい。

ので書き換えることにした。
```componentWillReceiveProps```の代用は```getDerivedStateFromProps```でいけるらしい。



#今まで
ツイッターみたいな「いいね」をreact native + rails + mysqlで実装するとき```componentWillReceiveProps```を使っていた。
(いまどきfirebase使えという話ではあるが、、)


![demo_181220.gif](https://qiita-image-store.s3.amazonaws.com/0/283557/e51a3525-d554-cc1f-4ef7-6526b9e31209.gif)


流れ的には
アイテムをいいねする(ハートをonPress)
→apiにrequest
→サーバ側でこのアイテムのステータスを(いいねされた状態に)変える
→レスポンスを返す
→レスポンスが返ってきたらハートの色を変える

である。

このレスポンスが返ってきたとき、propsが変化するのでこれをcomponentWillReceivePropsでキャッチしていた。

具体的には以下。

```good.js
import React, { Component } from 'react';
...

type Props = {
...
}

class Good extends Component<Props> {
 state = {
   isGood: false, <-- アイテムがいいねされているか
 }

 componentDidMount() {
  ... 
  this.setState({
    isGood, <--- 初期状態でapiからいいねされているか取ってきてsetStateする
  });
 }

 componentWillReceiveProps(nextProps) {
   const { isGood } = nextProps;
   this.setState({
     isGood,  <--- いいねした時apiからレスポンス返ってきたらsetStateする
   });
 }

 render() {
   const { isGood } = this.state;
   return (
    ...
    <TouchableOpacity
      onPress{() => 
        this.setState({ 
          isGood: !isGood,
        });
        this.someAction(...);  <--いいねするaction 
      } 
    >
     ...     <--- いいねボタン(ハート)
    </TouchableOpacity>
    ...
   )
 }
```

こんな感じ。
わざわざ、storeで管理しているものをいちいちsetStateしているのはいいねして返ってくるまで時間がかかって、色が変わるのがラグく感じてしまうのをやめたかった。
昔のairbnbのいいねはこのタイプだった。
ツイッターはすぐ色変わる。

つまり、onPress内でまず、stateを更新してすぐ色変え、api通信から戻ってきた結果を```componentWillReceiveProps```内でsetStateしている、ということ。
onPress内でsetStateしないバージョンが上のgif(さっき載せたやつと同じ)
setStateすると下のようになる

![demo_181220.gif](https://qiita-image-store.s3.amazonaws.com/0/283557/e51a3525-d554-cc1f-4ef7-6526b9e31209.gif)

![demo_181220_1.gif](https://qiita-image-store.s3.amazonaws.com/0/283557/03d5d0bb-07f4-4d1d-0063-d2626b2ef8ff.gif)


これで一応やりたいことはできている。
ちなみに```componentWillUpdate```の中身でsetStateするのはメモリ食いつぶすので禁止されているのでここでは使えない。

# getDerivedStateFromPropsを使う
componentWillReceiveを使っていたところを書き換えた。

　


```good.js

 // componentWillReceiveProps(nextProps) {
 // const { isGood } = nextProps;
 //  this.setState({
 //   isGood,  <--- いいねした時apiからレスポンス返ってきたらsetStateする
 //  });
 //}

 static getDerivedStateFromProps(nextProps, prevState) {
   const { isGood } = nextProps;
   if (prevState.isGood !== isGood) { <--nextPropsが更新されたら
     return { isGood: isGood };
   }
   return {};
 }
```


nextPropsには新しいprops, prevStateには今のstateが自動的に入る。
nextPropsが更新されたらstateを書き換える。
これで全く同じことが実現できた。


```getDerivedStateFromProps```はstatic メソッドでこれを実行することは
```componentDidUpdate``` + setStateするのと同じことらしい。

# 【追記】reduxを使っていればgetDerivedStateFromPropsは不要
コメントをいただいて気づいた。
上で実現したかったことはreduxを使い、変数isGoodをstoreで管理していれば
getDerivedStateFromPropsは不要になる。
一例を書けば、以下のようになる。


```good.js
import React from 'react';
...

type Props = {
...
}

const Good = (props: Props) => {
  const { isGood } = props;
  return (
    <TouchableOpacity
      onPress{() => 
        this.setIsGood(!isGood) 　　　　　　　　<--これがないときはツイッターみたいにすぐ色変わらない
        this.updateIsGood(!isGood);  <--いいねするaction 
      } 
    >
     ...     <--- いいねボタン(ハート)
    </TouchableOpacity>
    ...
   )
 }
```

```actions/index.js
  export const setIsGood = isGood => ({
    type: SET_IS_GOOD,
    payload: isGood,
  });

   export const updateIsGood = isGood => ({
     postでapiにリクエスト飛ばす処理
      .then((res) => {    <-- resに いいねされているかに対応する　true or falseが入っているとする
        dispatch(setIsGood(res));  <-- storeに値をsetする
      .catch((err) => {
        dispatch(setIsGood(!isGood)) <-- レスポンスエラーの時、storeに変更前の値を入れる
      }
  });
```

```reducers/index.js
const initialState = {
  isGood: false,
};

export function isGoodState(state = initialState, action) {
  switch (action.type) {
    case SET_IS_GOOD:
      return {
        ...state,
        isGood: action.payload,
      };
     default:
       return state;
  }
}

...

const rootReducers = combineReducers({
  isGoodState,
  ...
})

export default rootReducers;
```

これならgetDerivedStateFromPropsはいらないし、
pure functionで書くことができる