1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React Hooks で遊ぶ( useState )

Last updated at Posted at 2025-11-27

はじめに :fish:

Python ブームが去って React ブームが再燃している @guppy0356 です。
Next や Remix を使ってアプリを作ると格好がいいですが、個人だと規模が大きくなりがちで完遂も難しい場合が多いです。
React Hooks をひとつずつ振り返ってスキルアップしていきます :muscle:

この記事では useState をピックアップします。

技術スタック

  • Vite
  • Storybook
  • node v20.19.5

useState とは?

useState は、関数コンポーネント内で 「状態(State)」を管理するための React Hook です。

通常のローカル変数はコンポーネントが再レンダリングされるたびに初期化されてしまいますが、useState で管理された値は、React によってレンダリング間で保持されます。

また、useState が返す更新関数(例: setCounter)を実行すると、React はそのコンポーネントを**再レンダリング(Re-render)**するようスケジュールします。つまり、「値の保持」と「UIの更新トリガー」という2つの役割を担っています。

プロジェクト作成

1. プロジェクトを作成

npm create vite@latest use-state -- --template react-ts
cd use-state
npm install

2. サンプルコードをリセット

単純な構造で検証したいので、サンプルコードは削除します。

rm public/vite.svg
rm src/App.css
rm src/assets/react.svg
rm src/index.css

3. useState をつかったカウンターを追加

src/main.tsx を次の内容に変更します。

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

src/App.tsx を次の内容に変更します。

import { useState } from "react"

function App() {
  const [counter, setCounter] = useState(0)

  const handleIncrease = () => {
    setCounter(counter => counter + 1)
  }

  const handleDecrease = () => {
    setCounter(counter => counter - 1)
  }

  return (
    <>
      <button onClick={handleIncrease}>+</button>
      <button onClick={handleDecrease}>-</button>

      <div>
        { counter }
      </div>
    </>
  )
}

export default App

npm run dev を実行するとカウンターを表示できます。

4. Storybook を追加

npm create storybook@latest

推奨を選んでください

◆  What configuration should we install?
│  ● Recommended: Component development, docs, and testing features.
│  ○ Minimal: Just the essentials for component development.

5. テストを追加

src/App.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within, expect } from 'storybook/test';
import App from './App';

const meta: Meta<typeof App> = {
  component: App,
  tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<typeof App>;

export const Default: Story = {};

export const InteractiveFlow: Story = {
  play: async ({ canvasElement, step }) => {
    const canvas = within(canvasElement);

    await step('初期表示は0である', async () => {
      await expect(canvas.getByText('0')).toBeInTheDocument();
    });

    await step('プラスボタンを押すとカウントが1に増える', async () => {
      const incrementBtn = canvas.getByRole('button', { name: '+' });
      await userEvent.click(incrementBtn);
      await expect(canvas.getByText('1')).toBeInTheDocument();
    });

    await step('マイナスボタンを押すとカウントが0に戻る', async () => {
      const decrementBtn = canvas.getByRole('button', { name: '-' });
      await userEvent.click(decrementBtn);
      await expect(canvas.getByText('0')).toBeInTheDocument();
    });
  },
};

6. テスト実行

npm run test:storybook

すべてのテストがパスしました :tada:

> use-state@0.0.0 test:storybook
> vitest run --project=storybook

No story files found for the specified pattern: src/**/*.mdx
info Using tsconfig paths for react-docgen
info Using tsconfig paths for react-docgen

 RUN  v4.0.14 /Users/akira/Project/private/use-state

info Using tsconfig paths for react-docgen
 ✓  storybook (chromium)  src/App.stories.tsx (2 tests) 239ms
   ✓ Default 192ms
   ✓ Interactive Flow 45ms

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  05:37:34
   Duration  2.07s (transform 0ms, setup 351ms, import 23ms, tests 239ms, environment 0ms)
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?