見出し画像

fp-tsライブラリとts-patternライブラリを利用したフォールバックを意識するデータ取得について~React Queryを使って~

こんにちわ。nap5です。

fp-tsライブラリとts-patternライブラリを利用したフォールバックを意識するデータ取得について紹介をしたいと思います。




フォールバックを意識するうえでまずはデータ取得で起きそうな状態を定義してしまいます。UIによっては部分欠損など考慮すると簡単には定義しづらい側面が出てくるので一発定義はなかなか難しいところがあります。

// https://qiita.com/saba_can00/items/696baa5337eb10c37342#%E3%81%A7%E3%81%AFenum-%E3%81%BF%E3%81%9F%E3%81%84%E3%81%AA%E8%A8%98%E8%BF%B0%E3%82%92%E3%81%97%E3%81%9F%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AB%E3%81%A9%E3%81%86%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B%E3%81%8B
export const Mode = {
  Loading: 'ローディング中...',
  Recovery: 'リカバリする',
  Refresh: 'リフレッシュする',
} as const
type Mode = typeof Mode[keyof typeof Mode]


次に定義した状態をもとにts-patternでマッチング処理を書いてしまいます。

const decideMode = ({
  data,
  error,
}: {
  data: UsersData | undefined
  error: ErrorData
}): Mode => {
  return match({ data, error })
    .when(
      ({ data, error }) => error,
      () => Mode.Recovery
    )
    .when(
      ({ data, error }) => !data,
      () => Mode.Loading
    )
    .otherwise(() => Mode.Refresh)
}


データ取得ですが、Userドメインをサンプルに以下のように実装しています。


const demoUser = {
  id: 1,
  name: 'Leanne Graham',
  username: 'Bret',
  email: 'Sincere@april.biz',
  address: {
    street: 'Kulas Light',
    suite: 'Apt. 556',
    city: 'Gwenborough',
    zipcode: '92998-3874',
    geo: {
      lat: '-37.3159',
      lng: '81.1496',
    },
  },
  phone: '1-770-736-8031 x56442',
  website: 'hildegard.org',
  company: {
    name: 'Romaguera-Crona',
    catchPhrase: 'Multi-layered client-server neural-net',
    bs: 'harness real-time e-markets',
  },
}

type UserData = typeof demoUser
export type UsersData = UserData[]

// https://jsonplaceholder.typicode.com/users
interface UserFactory {
  listUp(): TaskEither<ErrorData, UsersData>
}

class UserRepository implements UserFactory {
  private mode: 0 | 1 = 0
  constructor(mode: 0 | 1) {
    this.mode = mode
  }
  listUp(): TaskEither<ErrorData, UsersData> {
    return tryCatch<ErrorData, UsersData>(
      async () => {
        if (this.mode === 1) {
          dispatchCustomError()
        }
        const response: AxiosResponse<UsersData, ErrorData> = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        )
        const { data } = response
        return data
      },
      (error) => {
        return error as AxiosError
      }
    )
  }
}


実装したデータ取得処理をフック化します。pipeが結構大きくなる場合はサービス層に切り出したり検討します。

const USER_KEY = 'Users'
const userRepository = new UserRepository(0)
const useListUpUser = () => {
  const { data, error, refetch } = useQuery<UsersData, ErrorData>([USER_KEY], {
    queryFn: async () => {
      const result = await userRepository.listUp()()
      // const result = await pipe(userRepository.listUp()())
      if (isError(result)) {
        return Promise.reject(result.left)
      }
      return Promise.resolve(result.right)
    },
  })
  const disabled = useMemo(() => {
    return decideMode({ data, error }) === Mode.Loading
  }, [data, error])

  const buttonLabelName = useMemo(() => {
    return decideMode({ data, error })
  }, [data, error])

  const refresh = useCallback(() => {
    queryClient.invalidateQueries([USER_KEY])
    // queryClient.removeQueries([USER_KEY])
    // refetch()
  }, [])

  const recovery = useCallback(() => {
    queryClient.removeQueries([USER_KEY])
    refetch()
  }, [refetch])

  return useMemo(() => {
    return {
      data,
      error,
      refetch,
      disabled,
      decideMode,
      buttonLabelName,
      refresh,
      recovery,
    }
  }, [data, error, refetch, disabled, buttonLabelName, refresh, recovery])
}


最後にコンポーネントでフックを読み込んでおしまいです。

const Home = () => {
  const { data, error, disabled, buttonLabelName, recovery, refresh } =
    useListUpUser()

  if (error) {
    return (
      <div className='py-12 max-w-2xl mx-auto w-full'>
        <h1 className='text-3xl font-bold underline flex justify-center items-center'>
          Hello world!
        </h1>
        <NiceButton
          type='button'
          labelName={buttonLabelName}
          onClick={recovery}
          disabled={disabled}
        />
        <Spacer />
        <ShowMe data={error.response?.data} />
      </div>
    )
  }

  return (
    <div className='py-12 max-w-2xl mx-auto w-full'>
      <h1 className='text-3xl font-bold underline flex justify-center items-center'>
        Hello world!
      </h1>
      <NiceButton
        type='button'
        labelName={buttonLabelName}
        onClick={refresh}
        disabled={disabled}
      />
      <Spacer />
      <ShowMe data={data} />
    </div>
  )
}


デモコードです。


リカバリのパターンを確認したい場合はレポジトリとかでインスタンスな状態もって引数から制御したりできるとハンディかもです。

const userRepository = new UserRepository(1) // 0 or 1 ...


簡単ですが、以上です。


この記事が気に入ったらサポートをしてみませんか?