【React】画像の変更を動的に反映させるのに苦労した

React × Rails SPAの作成時に画像の変更を動的に反映させるのに苦労したのでアウトプット

## やりたいこと

以下のようにモーダル内のフォームからニックネームおよびアバター画像を変更し、非同期的にマイページにも反映させたい。![recommends-14-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/652172/4d522e3b-2513-cfd6-e67d-b632b9e7f337.gif)



フォーム送信では以下のようにaxiosを用いてAPIと通信した後、React-routerのnavigationを用いてマイページに遷移させている。

```jsx
formSubmit(e) {
    e.preventDefault()
    if (this.props.location.state.content == 'Edit Profile') {
      axios
      .patch('/api/v1/users', {user: {nickname: this.state.user.nickname, avatar: this.state.avatar }} )
      .then(response => {
        this.props.history.push('/mypage')
        // this.props.history.push('/mypage')
        return response
      })
```

遷移先では

```jsx
componentDidMount() {
    axios 
    .get('/api/v1/mypage')
    .then(response => {
      if (response.data.avatar) {
        this.setState({
          user: response.data.user,
          books: response.data.books,
          avatar: response.data.avatar
        })
      } else {
        this.setState({
          user: response.data.user,
          books: response.data.books,
          avatar: Sample
        })
      }
      return response
    })
  }
```

のようにcomponentDidMountを用いてAPIと通信しているので
ユーザー情報の更新→history.push→componentDidMount→画面に変更内容が反映

と処理が進む想定。
しかしこれでは画像のみ非同期で変わらなかった。なんで？？？

## 試したこと

### history.push時に値を遷移先に送る

```jsx
formSubmit(e) {
    e.preventDefault()
    if (this.props.location.state.content == 'Edit Profile') {
      axios
      .patch('/api/v1/users', {user: {nickname: this.state.user.nickname, avatar: this.state.avatar }} )
      .then(response => {
				this.props.history.push({pathname: '/mypage', state: {user: response.data.user, avatar: response.data.avatar}})
        // this.props.history.push('/mypage')
        return response
      })
```

こんな感じでpushの引数にstateとして渡すことで遷移先で取得できないか試してみた。
しかしこれではだめだった。

### ターミナルのログを見る→そもそもcomponentDidMountが動いていない？

ターミナルのログを見る限りそもそもcomponentDidMountが動いていなさそう？と気づく。
history.pushでcomponentにマウントしているつもりだったけどモーダルだからコンポーネントにはすでにmountしている扱いなのかな。

### componentDidUpdateでsetStateを用いる

history.pushで送った値は

```jsx
this.props.location.state
```

から取り出せるのでこれを使ってcomponentDidUpdateを使えばいいんじゃないか？と気づく。

```jsx
componentDidUpdate() {
    let updatedProps = this.props.location.state
    if (updatedProps) {
      if (updatedProps.avatar) {
        this.setState({
          avatar: updateProps.avatar
        })
      } 
    }
  }
```

しかしこの場合

```
Uncaught Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
```

というエラーが出てしまう。

調べた限りcomponentDidUpdateでsetStateをすると画面が再描画されるのでそのときにまたcomponentDidUpdateが呼び出されて無限ループに入っている？ような感じとみた。
(参考：https://bit.ly/3hclKYS)

### componentDidUpdateでsetStateを使わずにstateに値を代入する

setStateがダメならstateに値を代入すればいいじゃんってことで

```jsx
componentDidUpdate() {
    let updatedProps = this.props.location.state
    if (updatedProps) {
      if (updatedProps.avatar) {
        this.state.avatar = updatedProps.avatar
      } 
    }
  }
```

このように記述した。するとstateは変わったが画像自体に変化はなかった。

### 画像のsrc属性を直接触ってみる

いろいろ調べてみると画像を動的にjsで変化させる際にsrc属性に値を代入する、ということをしている記事をよく見かけたのでそれをやってみることに。

```jsx
componentDidUpdate() {
    let updatedProps = this.props.location.state
    if (updatedProps) {
      if (updatedProps.avatar) {
        this.state.avatar = updatedProps.avatar
        document.getElementById('avatar').src = this.state.avatar
      } 
    }
  }
```

avatar、はアバター画像が入っているimgタグのid。
これで無事画像そのものも非同期で変更できた。



## 感想

ちょっとしたフロントの実装だと思っていましたが思ったより細かいところで躓いてしまった。
Reactのライフサイクルとかもっと勉強しないとなーという気持ちになりました。