Help us understand the problem. What is going on with this article?

これからはFunction Componentですべて解決できる――というのはどうやら幻想だったようです。

何がしたかったのか

Reactには、Lazy Componentというものがあります。

MyComponent.tsx
import React, { FC } from 'react';

const MyComponent: FC = () => (
  <div>Hello LazyComponent!</div>
);

export default MyComponent;
MyApp.tsx
import React, { FC, Suspense, lazy } from 'react';

const MyComponent = lazy(() => import('./MyComponent'));

const MyApp: FC = () => (
  <div>
    <Suspense fallback={<div>Loading...</div>}>
      <MyComponent />
    </Suspense>
  </div>
);

export default MyApp;

とすると、MyComponentのロードが完了するまでfallbackに設定された<div>Loading...</div>を代わりにレンダリングしてくれるというものです。

で、いろいろ調べてたらこんなこともできると判明。

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

let result = null;
const timeout = (msec) => new Promise(resolve => {
  setTimeout(resolve, msec)
});

const LazyComponent = () => {
  if (result !== null) {
    return (
      <div>{result}</div>
    )
  }
  throw new Promise(async(resolve) => {
    await timeout(1000);
    result = 'Done'
    resolve();
  })
};

export default LazyComponent;

React 16.6の新機能、React.lazyとReact.Suspense を使った非同期処理

こう書いたら、throwしたPromiseがresolveされたときにもう1回レンダリングされるらしく。私の探し方が悪いのか何なのか、この仕様はReactのドキュメント上で見つけることができませんでした。どこに書いてあるのか知っている人がいたらこっそり教えてほしいです。

それはさておきこの仕様、ドキュメントで見つからなかったので動かない前提で試しに書いてみました。

試しに書いたコード
import React, { FC, lazy, Suspense } from 'react';

const PromiseTest= lazy(async () => {
  let state = 0;
  const TestInner: FC = () => {
    if(state) {
      return (
        <div>Done! {state}</div>
      )
    }
    throw new Promise((res) => {
      setTimeout(() => {
        state = 5;
        res();
      }, 5000);
    });
  };
  return {
    default: TestInner,
  };
});

const TestApp: FC = () => {
  return (
    <div>
      <Suspense fallback={<div>WAITING...</div>}>
        <PromiseTest />
      </Suspense>
    </div>
  );
}

やってみた結果……動く!動くぞ!

さて、問題のコードに移ろうじゃないか

さて、Promiseをthrowしたら期待通りに動くことが分かったんですけれど。state = 5ってPromiseの中で変数に代入しちゃってるじゃないですか。ぶっちゃけキモいですよね。
useStateフックに置き換えてもいけるんじゃね?って思った私、置き換えてみました。

置き換えてみた
import React, { FC, lazy, Suspense, useState } from 'react';

const PromiseTest= lazy(async () => {
  const TestInner: FC = () => {
    const [state, setter] = useState(0);
    if(state) {
      return (
        <div>Done! {state}</div>
      )
    }
    throw new Promise((res) => {
      setTimeout(() => {
        setter(5);
        res();
      }, 5000);
    });
  };
  return {
    default: TestInner,
  };
});

const TestApp: FC = () => {
  return (
    <div>
      <Suspense fallback={<div>WAITING...</div>}>
        <PromiseTest />
      </Suspense>
    </div>
  );
}

あれ、動かん:thinking::thinking::thinking:
動かんぞ。

useStateに置き換える前は動いたコードが、置き換えた瞬間動かなくなりました。てゆうか、setterは普通に呼ばれているはずなのに、stateの値は0のまま。なんでや、、、。

諦めてComponent classにしてみた

というわけで、PromiseTestの実装をComponent classに変えてみました。statethis.state.statesetterthis.setStateに変えただけですけどね。

classに書き換えてみた
class TestInner extends React.Component<{}, { state: number }> {
  constructor(props: {}) {
    super(props);

    this.state = {
      state: 0,
    };
  }

  render() {
    if(this.state.state) {
      return (
        <li>Done! {this.state.state}</li>
      );
    }
    throw new Promise((res) => {
      setTimeout(() => {
        console.log('resolved');
        this.setState({ state: 5 });
        res();
      }, 5000);
    });
  }
}

const PromiseTest = lazy(async () => {
  return {
    default: TestInner,
  };
});

const TestApp: FC = () => {
  return (
    <div className='board-list-container'>
      <Suspense fallback={<div>WAITING...</div>}>
        <PromiseTest />
      </Suspense>
    </div>
  );
}

こうすると、動きました。動いてしまいました。え、なんでなんや、、、:thinking:
……Function Componentが使えない極めてまれなケースの1つを発見した身としては、非常に頭が痛いです。こういう重要なことはもっとわかりやすくドキュメントに書いておいて下せぇ……。

結論

React、なんもわからん。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした