3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

やりたいこと

以下のようにモーダル内のフォームからニックネームおよびアバター画像を変更し、非同期的にマイページにも反映させたい。recommends-14-1.gif

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

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
      })

遷移先では

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時に値を遷移先に送る

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で送った値は

this.props.location.state

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

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に値を代入すればいいじゃんってことで

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

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

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

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

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のライフサイクルとかもっと勉強しないとなーという気持ちになりました。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?