Next.js と TypeScript で、useState を理解する

TwitterFacebookHatena

TL;DR

このページでは、Next.js と TypeScript の実装方法について深掘りし、React の Hooks の一つである useState の使い方を解説します。useState の基本的な概念から高度なテクニックまで、初心者から上級者まで参考になる内容を提供します。

開発環境 バージョン
Next.js 13.4.3
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

useState は、状態管理

useState は React の Hooks の一つで、関数コンポーネントの中で状態を管理するためのものです。もし、状態を持たせたいコンポーネントがあれば、useState を使うと便利です。

では、まず「状態」とは何か、そしてそれがなぜ必要なのか、それを知るための一つの例を考えてみましょう。

皆さんは、スーパーマーケットで買い物をした経験があると思います。スーパーマーケットにはたくさんの商品が並んでいますよね。それぞれの商品は、あなたがそれをカートに入れるまでは「買わない状態」にあります。

でも、あなたが「おいしいそうなパン」を見つけてカートに入れたとき、そのパンは「買う状態」に変わります。また、カートに入れたパンを「やっぱりこれはいらないな」と思ってカートから出すと、そのパンは再び「買わない状態」に戻ります。

このように、あなたがスーパーマーケットで買い物をするときの「商品の買う/買わない」の状態のように、アプリケーションでも多くのものが「ある状態」から「別の状態」に変化します。そして、それらの状態を管理するのが「useState」なんです。

例えば、ユーザーがフォームに何かを入力したとき、その入力値は状態として保存され、その後ユーザーが別の値を入力すると、その新しい値で状態が更新されます。このように useState を使うと、アプリケーションの状態を簡単に管理できるんです。

ですから、React でアプリケーションを作るとき、useState はとても大切なツールなんですよ。

基本的な使用方法は以下の通りです。

import { useState } from 'react'

type Props = {
  initialCount: number
}

const Counter = ({ initialCount }: Props) => {
  const [count, setCount] = useState(initialCount)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  )
}

useState は一つの引数を取り、その引数は状態の初期値です。この例では、初期値は initialCount として渡されます。

また、useState は 2 つの要素を持つ配列を返します。一つ目の要素は現在の状態、二つ目の要素はその状態を更新する関数(セッター)です。この例では、現在のカウント値が count という名前で、それを更新する関数が setCount という名前で取り扱われています。

Next.js での useState の使用例

では、Next.js プロジェクトで useState をどのように使うのか具体的に見てみましょう。この例では、ユーザーの入力を状態として保持し、それを表示するシンプルなフォームを作成します。

ファイル名:components/InputForm.tsx

import { useState } from 'react'

type Props = {
  placeholder: string
}

const InputForm = ({ placeholder }: Props) => {
  const [input, setInput] = useState('')

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setInput(event.target.value)
  }

  return (
    <div>
      <input type="text" value={input} onChange={handleInputChange} placeholder={placeholder} />
      <p>入力値: {input}</p>
    </div>
  )
}

export default InputForm

このコンポーネントでは、入力要素の値が状態として保持され、ユーザーが入力するたびにその状態が更新されます。具体的には、ユーザーの入力に応じて input の値が更新され、その更新された値が画面上に表示されます。このように useState を使うことで、ユーザーのアクションに応じて動的に変化する UI を簡単に実装することができます。

useState を使って配列を更新する

useState を使って配列の要素を追加する方法について解説しますね。まずは、「配列」というものを理解しましょう。

「配列」とは、まるで小さな引き出しのようなもので、その中にいろいろなものを保存することができます。それぞれの引き出しには番号がついていて、その番号を使って、どの引き出しに何が入っているかを知ったり、新しいものを入れたり、入っているものを取り出したりします。

では、React の useState を使って、配列に新しいものを追加する方法を見てみましょう。まずは新しい空の配列(空の引き出しセット)を作ります。

const [items, setItems] = useState([])

次に、新しいもの(例えば、文字列の 'apple')を配列に追加します。

setItems([...items, 'apple'])

このコードは、「今までの items(引き出しの中身全て)に'apple'を追加した新しい配列を作って、それを items に保存する」という意味です。配列の最後に新しい要素が追加されます。

次に、配列から特定の要素を削除する方法を見てみましょう。ここでは、2 番目の要素(番号は 0 から始まるので、実際には'apple')を削除します。

setItems(items.filter((item, index) => index !== 1))

このコードは、「items の中で、番号が 1 でないものだけを取り出して新しい配列を作り、それを items に保存する」という意味です。つまり、番号 1 の要素が削除されます。

最後に、配列の要素を変更する方法を見てみましょう。ここでは、1 番目の要素を'orange'に変更します。

setItems(items.map((item, index) => (index === 0 ? 'orange' : item)))

このコードは、「items の中の各要素について、その番号が 0 なら'orange'に変更し、それ以外ならそのままにして新しい配列を作り、それを items に保存する」という意味です。

以上が、useState を使って配列の要素を追加、削除、変更する方法です。これらの知識を使って、アプリケーションを作るときに色々なことができるようになりますよ。

複数の useState を使用

複数の独立した状態を持つコンポーネントを作成する場合、複数の useState を使用することがあります。例えば、名前と年齢を入力するフォームを作成する場合、以下のようになります。

ファイル名:components/MultipleInputForm.tsx

import { useState } from 'react'

const MultipleInputForm = () => {
  const [name, setName] = useState('')
  const [age, setAge] = useState('')

  const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setName(event.target.value)
  }

  const handleAgeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setAge(event.target.value)
  }

  return (
    <div>
      <input type="text" value={name} onChange={handleNameChange} placeholder="名前" />
      <input type="text" value={age} onChange={handleAgeChange} placeholder="年齢" />
      <p>名前: {name}</p>
      <p>年齢: {age}</p>
    </div>
  )
}

export default MultipleInputForm

このように、各入力フィールドごとに独立した状態(nameage)とその更新関数(setNamesetAge)が定義されています。

Emotion でのスタイリング

Emotion を使用して、React コンポーネントにスタイルを適用することも可能です。次の例では、入力フォームのスタイルを変更してみましょう。

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import { useState } from 'react'

const inputStyle = css`
  margin: 10px 0;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 16px;
`

const InputFormStyled = ({ placeholder }: { placeholder: string }) => {
  const [input, setInput] = useState('')

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setInput(event.target.value)
  }

  return (
    <div>
      <input type="text" value={input} onChange={handleInputChange} placeholder={placeholder} css={inputStyle} />
      <p>入力値: {input}</p>
    </div>
  )
}

export default InputFormStyled

上記の例では、inputStyle を定義して、それを入力要素に適用しています。こうすることで、特定のスタイルを複数の要素に適用したり、スタイルを動的に変更することが可能になります。

オブジェクトを受け取る

useStateはオブジェクトも受け取ることが可能です。useStateは初期値を受け取る関数で、その初期値はどんな形でも構いません。つまり、文字列、数値、配列、オブジェクトなど、JavaScript で作成できるあらゆる形のデータをuseStateの初期値として使うことができます。

例えば、次のようにuseStateを使ってオブジェクトを管理することができます。

type PersonProps = {
  name: string
  age: number
}

const Component = () => {
  const [person, setPerson] = useState<PersonProps>({ name: 'John', age: 20 })

  const updateName = () => {
    setPerson((prevPerson) => ({ ...prevPerson, name: 'Jane' }))
  }

  const updateAge = () => {
    setPerson((prevPerson) => ({ ...prevPerson, age: 30 }))
  }

  return (
    <div>
      <div>{person.name}</div>
      <div>{person.age}</div>
      <button onClick={updateName}>Update Name</button>
      <button onClick={updateAge}>Update Age</button>
    </div>
  )
}

この例では、useStatePersonProps型のオブジェクトを初期値としています。そして、setPerson関数を使ってオブジェクト内のnameageを更新しています。

オブジェクトを更新する際は、新しいオブジェクトを作成するようにします。これは React の仮想 DOM が変更を検出しやすくするためです。そのため、オブジェクト内の特定の値を更新する際には、スプレッド構文...を使ってオブジェクトをコピーし、その後で特定の値を更新します。

以上が、Next.js と TypeScript を使用して useState を実装する方法になります。これらの知識を活用して、様々な状況に応じた動的な UI を実装していきましょう。また、Emotion を使用すれば、スタイリングも非常に簡単に行えるので、ぜひ試してみてくださいね。

Next.js と TypeScript で、useState を理解する