Skip to content
Back to skills

Async State Type

ASecurity

Avoid contradictory state types for async data fetching. Use Suspense or discriminated unions instead. Use when writing or reviewing async data fetching code in React/TypeScript.

  • 3 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added May 27, 2026
data-aitypescriptgoreactgitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add ncaq/konoka --skill async-state-type --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Async State Type?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Async State Type
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ncaq-async-state-type/badge)](https://www.skillsdirectory.com/skills/ncaq-async-state-type)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: async-state-type
description: Avoid contradictory state types for async data fetching. Use Suspense or discriminated unions instead. Use when writing or reviewing async data fetching code in React/TypeScript.
user-invocable: false
---

# 非同期データ取得の型設計

## 矛盾した状態を許容する型を避ける

非同期データ取得において以下のような型設計は避けてください。

```typescript
// Bad: dataにアクセス可能なのにloading/errorも同時に存在しうる
type AsyncState<T> = {
  data: T | undefined;
  loading: boolean;
  error: Error | undefined;
};
```

この型は以下の問題を持ちます。

- `data`が存在するのに`loading: true`という矛盾した状態を型が許容します
- `data`と`error`が同時に存在する状態を型が許容します
- 実行時の状態遷移に依存しており、型レベルでの安全性がありません

## 推奨されるアプローチ

### Suspenseパターンを使う

React Suspenseを使えば、ローディング中はコンポーネントがレンダリングされないため、
`data`の型が確定します。

```tsx
// Good: useSuspenseQueryはdataがT型で確定している
function UserList() {
  const { data } = useSuspenseQuery({
    queryKey: ["users"],
    queryFn: fetchUsers,
  });
  // data: User[] (undefinedにならない)
  return (
    <ul>
      {data.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// 親コンポーネントでSuspenseとErrorBoundaryを使う
<ErrorBoundary fallback={<ErrorMessage />}>
  <Suspense fallback={<Loading />}>
    <UserList />
  </Suspense>
</ErrorBoundary>;
```

### 判別共用体(Discriminated Union)を使う

Suspenseが使えない場合は、状態を排他的に表現する判別共用体を使ってください。

```typescript
// Good: 状態が排他的に表現されている
type AsyncState<T> =
  | { status: "loading" }
  | { status: "error"; error: Error }
  | { status: "success"; data: T };
```

`status`フィールドで分岐することで、各状態で利用可能なフィールドが型レベルで保証されます。

```tsx
function renderState(state: AsyncState<User[]>) {
  switch (state.status) {
    case "loading":
      return <Loading />;
    case "error":
      return <ErrorMessage error={state.error} />;
    case "success":
      return <UserList users={state.data} />;
  }
}
```

[Effect](https://effect.website/)の`Schema`を使えば、
APIレスポンスのデコード時に型を確定させつつ、
失敗を`Exit`や`Either`で型安全に表現できます。
判別共用体を手書きする代わりにEffectの仕組みに乗せるのも良いでしょう。

## ライブラリの関数選定時の注意

データ取得ライブラリを使用する場合、
Suspense対応のAPIが存在するならそちらを優先して使用してください。

例えば、
[apollo-client](https://github.com/apollographql/apollo-client)や、
[TanStack Query](https://tanstack.com/query/latest)なら、
`useQuery`ではなく`useSuspenseQuery`を使います。

Suspense対応APIは`data`が`T | undefined`ではなく`T`で返るため、型安全です。

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.