1. ホーム
  2. javascript

[解決済み] React useEffectが発生する。マウントされていないコンポーネントに対してReactの状態更新を実行できない

2022-09-27 04:13:27

質問

データを取得するときに、次のようなメッセージが表示されます。アンマウントされたコンポーネントに対して React の状態更新を実行できません。アプリはまだ動作しますが、reactは私がメモリリークを引き起こしている可能性があることを示唆しています。

これはダメ押しですが、アプリケーションにメモリリークがあることを示しています。修正するには、useEffectクリーンアップ関数ですべてのサブスクリプションと非同期タスクをキャンセルします."。

なぜこの警告が出続けるのですか?

これらの解決策を調べてみました。

https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal

https://developer.mozilla.org/en-US/docs/Web/API/AbortController

と表示されますが、これでも警告が表示されました。

const  ArtistProfile = props => {
  const [artistData, setArtistData] = useState(null)
  const token = props.spotifyAPI.user_token

  const fetchData = () => {
    const id = window.location.pathname.split("/").pop()
    console.log(id)
    props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
    .then(data => {setArtistData(data)})
  }
  useEffect(() => {
    fetchData()
    return () => { props.spotifyAPI.cancelRequest() }
  }, [])
  
  return (
    <ArtistProfileContainer>
      <AlbumContainer>
        {artistData ? artistData.artistAlbums.items.map(album => {
          return (
            <AlbumTag
              image={album.images[0].url}
              name={album.name}
              artists={album.artists}
              key={album.id}
            />
          )
        })
        : null}
      </AlbumContainer>
    </ArtistProfileContainer>
  )
}

編集します。

私のapiファイルでは AbortController() を追加し signal を使用しているので、リクエストをキャンセルすることができます。

export function spotifyAPI() {
  const controller = new AbortController()
  const signal = controller.signal

// code ...

  this.getArtist = (id) => {
    return (
      fetch(
        `https://api.spotify.com/v1/artists/${id}`, {
        headers: {"Authorization": "Bearer " + this.user_token}
      }, {signal})
      .then(response => {
        return checkServerStat(response.status, response.json())
      })
    )
  }

  // code ...

  // this is my cancel method
  this.cancelRequest = () => controller.abort()
}

私の spotify.getArtistProfile() は次のようになります。

this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
  return Promise.all([
    this.getArtist(id),
    this.getArtistAlbums(id,includeGroups,market,limit,offset),
    this.getArtistTopTracks(id,market)
  ])
  .then(response => {
    return ({
      artist: response[0],
      artistAlbums: response[1],
      artistTopTracks: response[2]
    })
  })
}

で解決されますが、私のシグナルは個々のapi呼び出しに使用されるので Promise.all で解決されます。 abort() という約束ができないので、常に状態を設定することになります。

どのように解決するのか?

共有する AbortController の間に fetch() のリクエストは正しいアプローチです。

いつ どんな Promise は中止されます。 Promise.all() で拒否されます。 AbortError :

function Component(props) {
  const [fetched, setFetched] = React.useState(false);
  React.useEffect(() => {
    const ac = new AbortController();
    Promise.all([
      fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
      fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
    ]).then(() => setFetched(true))
      .catch(ex => console.error(ex));
    return () => ac.abort(); // Abort both fetches on unmount
  }, []);
  return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms
<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>