こんにちは、新卒エンジニアの鈴木です!
せっかくバックエンドでAPIを完成させたのに、どうやってReactで表示すればいいんだ? と手が止まってしまった経験はありませんか?
「どのタイミングでAPIを叩けばいいの?」
「取得したデータをどうやってコンポーネントに渡すの?」
「調べたけど非同期処理ってなんだ?」
非同期処理という言葉を聞くだけで、なんだか難しそうだと感じてしまいますよね。
でも、ご安心ください! Reactの基礎知識だけで、驚くほどシンプルにAPI通信を実装できるんです。
この記事では、APIをReactアプリケーションから呼び出し、取得したデータを画面に表示するまでの一連の流れを、ステップバイステップで丁寧に解説します。
開発の準備
Reactプロジェクトの準備
まずは、Reactのプロジェクトを作成しましょう。今回は、高速な開発環境で注目されているVite(ヴィート)というツールを使います。
Viteを初めて使う方や、詳しく知りたいという方は、さきにVite公式サイトで概要を確認してみてください。
準備ができたら、作業したいフォルダ内で以下のコマンドを実行してください。
C:\Users\鈴木 伊織\Desktop\doc>npm create vite@latest
> npx
> create-vite
│
◇ Project name:
│ wordbook-react-blog
│
◇ Select a framework:
│ React
│
◇ Select a variant:
│ JavaScript
│
◇ Scaffolding project in C:\Users\鈴木 伊織\Desktop\doc\wordbook-react-blog…
│
└ Done. Now run:
cd wordbook-react-blog
npm install
npm run dev
これだけで準備は完了です! 簡単ですね!
呼び出し先のAPIの確認
前回作成したAPIを使います。ぜひ以下記事から確認をお願いします!

Express.js x PostgreSQLではじめての認証付きAPI構築
エンドポイント | 説明 | Tokenの有無 | |
---|---|---|---|
ログイン(POST) | /auth/login | usernameとpasswordを使ってログインする。ログインに成功するとtokenが返される | 無し |
単語一覧取得(GET) | /words | DBに登録されている単語を全部取得する | 無し |
単語新規追加(POST) | /words | 新しく単語を登録する | 有り |
単語1件取得(GET) | /words/:id | DBに登録されている指定された単語を1件取得する | 有り |
単語更新(PUT) | /words/:id | 指定された単語を任意のものに更新する | 有り |
単語削除(DELETE) | /words/:id | 指定された単語を削除する | 有り |
実装
さっそく書いていきましょう。
以下は簡単なログイン画面のコンポーネントの例です。こちらを使ってログイン処理を実装していきましょう。
//src/App.jsx import { Login } from "./components/pages/login/login" function App() { return ( <> <Login /> </> ); } export default App
//src/components/pages/login/login.jsx import { useState } from "react"; export function Login() { // state const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); // handlers const handleSubmit = (event) => { event.preventDefault(); // API処理をここで行う } return ( <> <h2>Login Page</h2> <form onSubmit={handleSubmit}> <div> <label htmlFor="username">Username:</label> <input type="text" id="username" value={username} required onChange={(event) => setUsername(event.target.value)} /> </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" value={password} required onChange={(event) => setPassword(event.target.value)} /> </div> <button type="submit">Login</button> </form> </> ); }
Ajaxの実装
ログイン画面のUIができたのでAPIの処理を実装していきます。
さまざまな場所でAPIを呼び出すたびにfetch
を記述するのは大変ですし、コードも重複してしまいます。そこで、ページをリロードせずにサーバーと通信するAjaxという技術を実現するために、fetch
を使った便利な共通関数を作成します。
//src/utils/Ajax.js export async function Ajax(url = "", method = 'GET', bodyData, token,) { const BASE_URL = "http://localhost:3001/api/"; const opt = { method, headers: { "Content-Type": "application/json", } }; // If a token is provided, add it to the headers if (token) { opt.headers.Authorization = `Bearer ${token}`; } // If bodyData is provided, add it to the body if (bodyData) { opt.body = JSON.stringify(bodyData); } const response = await fetch(`${BASE_URL}${url}`, opt); return response.json(); }
この関数を使えば、必要な情報を引数として渡すだけでAPIリクエストが完了し、毎回同じコードを書く手間が省けてコンポーネントもスッキリします。こちら使って、APIを実装していきましょう。
ログイン機能の実装
さきほどのhandleSubmit
に追記します。
//src/components/pages/login/login.jsx > handleSubmit const handleSubmit = (event) => { event.preventDefault(); // API処理をここで行う Ajax('auth/login', 'POST', { username, password }, null) .then(res => { if(res.token) { console.log('Login successful:', res); } else { alert('Login failed: ' + res.message); } }); }
作成したAjax
関数を使い、ログイン処理を実装しています。
ログインAPIの仕様に合わせて、引数にはエンドポイントのパス('auth/login'
)、メソッド('POST'
)、そしてリクエストボディとしてusername
とpassword
のオブジェクトを渡します。
Ajax
関数はPromise
を返す設計なので、.then()
でレスポンスを受け取ります。レスポンスの中にtoken
が含まれていればログイン成功と判断し、そうでなければ失敗としてアラートを表示する、という流れです。
コンテキストの実装
ログインの実装が完了しました。
ログインに成功すると、APIサーバーからtoken
が返ってきます。このtoken
は、今後単語の追加や削除といった認証が必要なAPIを呼び出す際に必ず必要になります。
しかし、token
をLogin
コンポーネントだけで持っていても、他のコンポーネントから利用できません。そこで、ReactのContextを使います。
Contextを使えば、コンポーネントの親子関係を気にすることなく、アプリケーション全体でのユーザー情報や現在の表示画面といったデータを簡単に共有できるようになります。
//src/components/appContext.jsx import { createContext, useState } from "react"; import useStorage from "../hooks/useStorage"; export const AppContext = createContext(); function AppContextProvider({ children }) { // appの状態管理 const [appState, setAppState] = useState('login'); // ユーザ情報の状態管理 const [user, setUser] = useStorage('user', { userId: null, token: null }}); const ctx = { appState, setAppState, user, setUser, }; return ( <AppContext.Provider value={ctx}> {children} </AppContext.Provider> ); } export default AppContextProvider;
さらに今回は、ブラウザをリロードしてもログイン状態が消えないように、useStorage
というカスタムフックを使い、取得した情報をブラウザのlocalStorage
に保存する処理も加えています。
//src/hooks/useStorage import { useState } from "react"; function useStorage(key, defaultValue) { // storage check const item = window.localStorage.getItem(key); // if item is not found, use defaultValue const [value, setValue] = useState( item ? JSON.parse(item) : defaultValue ); // setValue is a function to update the state and localStorage const set = (value) => { setValue(value); window.localStorage.setItem(key, JSON.stringify(value)); }; return [value, set]; } export default useStorage;
main.jsx
に記述することもお忘れなく。
//src/main.jsx > createRoot createRoot(document.getElementById('root')).render( <StrictMode> <AppContextProvider> <App /> </AppContextProvider> </StrictMode> )
これでappState
とuser
情報を簡単に参照できるようになりました。こちらを使用して、クライアント側の処理を実装していきます。
まずLogin
コンポーネントがappState
が'login'
のときにしか表示されないようにします。
useContext
を使用してappState
を受け取り表示するコンポーネントを切り替えられるようにします。また、user
を参照してログイン済みであればsetAppState
を呼び出して自動的に単語一覧画面へ遷移させます。
//src/app.jsx import { useContext } from "react"; import { AppContext } from "./components/appContext"; import { Login } from "./components/pages/login/login" function App() { const { appState, setAppState, user } = useContext(AppContext); useEffect(() => { if(user?.token) { // ユーザがログインしている場合は一覧画面へ遷移 setAppState('wordList'); } }, [user]); return ( <> {appState === 'login' && <Login />} //appStateがloginのときのみ表示する </> ); } export default App;
いよいよログイン処理が完成します。さきほどのhandleSubmit
関数を以下のようにしてください。
// handlers const handleSubmit = (event) => { event.preventDefault(); // API処理をここで行う Ajax('auth/login', 'POST', { username, password }, null) .then(res => { if(res.token) { setUser({userid: res.userId, token: res.token}); //user情報を登録する setAppState('wordList'); //appStateを変更する } else { alert('Login failed: ' + res.message); } }); }
これでログインに成功したときuser
の情報とappState
が更新されます。
appState
が更新されたことによって画面に何も表示されなくなりますが、正常に動作しています。
一覧取得の実装
続いて単語一覧取得画面を作成していきます。
//src/components/pages/word/wordList.jsx import { useContext, useState, useEffect } from "react"; import { AppContext } from "../../appContext"; import { Ajax } from "../../../utils/Ajax"; import { WordCard } from "./components/wordCard"; export function WordList() { const [wordList, setWordList] = useState([]); const { setAppState, setActiveId } = useContext(AppContext); const handleClick = (word) => { setActiveId(word.id); setAppState('WordDetail'); } useEffect(() => { Ajax('words') .then(res => { if(res.length > 0) { setWordList(res); } }); }, []); return ( <> <h2>Word List</h2> <div> <ul> {wordList.map((word) => ( <li key={word.id}> <WordCard word={word} onClick={handleClick} /> </li> ))} </ul> </div> </> ); }
これだけで一覧取得をすることができます。Ajax
でログイン処理と大きく違うところは、引数にURL
しか渡していないところです。
method
にはデフォルト値で'GET'
を指定しているため引数に含める必要がなく、bodyData
もtoken
も不要なためこの記述ができるようになります。
別のアプリケーションで使用するときは、Ajax
の引数の順番を変えることで、より引数の少ない実装を考えるのが楽しかったりします。
今後1件取得時や単語の編集・削除のAPIを実装します。これらの機能では、どの単語に対して操作を行うかをが正確に把握している必要があります。このどの単語に対して行うかの変数をContextに定義しておきます。
//src/components/appContext.jsx //省略 //選択された単語IDを管理 const [activeId, setActiveId] = useState(null); const ctx = { appState, setAppState, user, setUser, activeId, //追加 setActiveId //追加 } //省略
//src/components/pages/word/components/wordCard.jsx import { useState } from "react"; export function WordCard({ word, onClick }) { const [isOpen, setIsOpen] = useState(false); const descOpen = (event) => { event.stopPropagation(); setIsOpen(!isOpen); } return ( <div onClick={() => onClick(word)}> <h3>{word.word}</h3> { isOpen && <p>{word.description}</p> } <button onClick={(event) => descOpen(event)}> {isOpen ? 'close' : 'open'} </button> </div> ); }
一覧画面の準備が整ったのでapp.jsx
に記述します。
//src/app.jsx > return return ( <> {appState === 'login' && <Login />} {appState === 'wordList' && <WordList />} </> );
こうすることで
appState
が'wordList'
のときに一覧画面が表示されるようになります。
ログイン処理が成功したら一覧画面が表示されることを確認してください。
また、一度ログイン状態になるとリロードしてもログイン画面が表示されないようになっているはずです!
追加機能の実装
続いて追加機能を実装します。一覧画面に登録画面に遷移するボタンを配置します。
//src/components/pages/word/wordList.jsx //省略 const handleClick = (word) => { setActiveId(word.id); setAppState('wordDetail'); } const handleClickAdd = () => { //追加 setAppState('wordAdd'); } useEffect(() => { Ajax('words') .then(res => { if(res.length > 0) { setWordList(res); } }) }, []); return ( <> <h2>Word List</h2> <div> <button type="button" onClick={handleClickAdd}>add</button> //追加 <ul> //省略
次に追加画面を作成します。以下のコードを記述してください。
//src/components/pages/word/wordAdd.jsx import { useState, useContext} from 'react'; import { Ajax } from '../../../utils/Ajax'; import { AppContext } from '../../appContext'; export function WordAdd() { const [word, setWord] = useState(''); const [description, setDescription] = useState(''); const { setAppState, user, setUser } = useContext(AppContext); const handleBack = () => { setAppState('wordList'); }; const handleSubmit = (event) => { event.preventDefault(); Ajax('words', 'POST', {word, description}, user.token) .then(res => { if(res.message === 'Token expired') { alert('Token expired. Please log in again.'); setUser({userId: null, token: null}); setAppState('login'); } else { alert('Word added successfully'); setAppState('wordList'); } }) .catch(err => { alert('Error adding word:', err); }); } return( <> <h2>Word Add</h2> <button type="button" onClick={handleBack}>back</button> <form onSubmit={handleSubmit}> <div> <label>Word:</label> <input type="text" id="word" value={word} onChange={(event) => setWord(event.target.value)} placeholder="Enter word" /> </div> <div> <label>Description:</label> <input type="text" id='description' value={description} onChange={(event) => setDescription(event.target.value)} placeholder="Enter description" /> </div> <button type="submit">add</button> </form> </> ); }
これはform
に入力されたデータを基にAPIを実行しています。
単語の追加はログインしているユーザーのみに許可された操作のため、Ajax
関数の第4引数にuser.token
を渡して認証情報をサーバーに送っています。
認証失敗時にはuser
情報を削除し、再度ログインを求めるようログイン画面へ誘導しています。正常に登録された場合は一覧画面へ遷移します。
これだけで追加処理は完成です。
詳細画面の実装
続いて詳細画面を作成していきます。
一覧画面で単語をクリックすると詳細画面が表示される処理は書いてあるので、さっそく実装していきます。
//src/components/pages/word/wordDetail.jsx import { useContext, useEffect, useState } from "react"; import { Ajax } from "../../../utils/Ajax"; import { InputField } from "./components/InputField"; import { AppContext } from "../../appContext"; export function WordDetail() { const [word, setWord] = useState(); const [description, setDescription] = useState(); const { setAppState, user, setUser, activeId } = useContext(AppContext); const handleBack = () => { setAppState('wordList'); }; const handleSubmit = (event) => { event.preventDefault(); // API処理をここで行う(PUT) }; const handleDelete = () => { // API処理をここで行う(DELETE) } useEffect(() => { Ajax(`words/${activeId}`) .then(res => { setWord(res.word); setDescription(res.description); }); }, []); return ( <> <h2>WordDetail</h2> <button type="button" onClick={handleBack}>back</button> <form onSubmit={handleSubmit}> <InputField value={word} onChange={(event) => setWord(event.target.value)} tagName="h3" /> <InputField value={description} onChange={(event) => setDescription(event.target.value)} tagName="p" /> <br/> <button type="submit">update</button> </form> <button type="button" onClick={handleDelete}>delete</button> </> ); }
上のコードでは、詳細画面が表示された瞬間にactiveId
に基づいて1件取得するAPIを実行しています。一覧画面からprops
でもらってくる方法もありますが、APIを実行することでより最新のデータを取得することができます。
//src/components/pages/word/components/inputField.jsx import { useState, useRef } from "react"; export function InputField({ value, onChange, tagName}) { const inputRef = useRef(null); //動的にタグを切り替える const Tag = tagName || 'span'; const [isInput, setIsInput] = useState(false); const inputStart = () => { setIsInput(true); setTimeout(() => { if (inputRef.current) { inputRef.current.focus(); } }, 0); }; return ( <> {isInput ? ( <input type="text" value={value} onChange={onChange} onBlur={() => setIsInput(false)} onKeyDown={(event) => event.key === 'Enter' && setIsInput(false)} ref={inputRef} /> ) : ( <Tag onClick={() => inputStart()}>{value}</Tag> )} </> ); }
上のコンポーネントでは、テキストをクリックするとその場で編集できるUIを、Stateによる条件分岐で実現しています。
変更機能の実装
さきほど詳細画面でform
を実装してしまったためこちらではAPIの処理を書くだけで良いです。
処理自体も単語追加とはmethod
が違うだけで要領は変わりません。
wordDetail.jsx
に以下のコードを記述してください。
//src/components/pages/word/wordDetail.jsx > handleSubmit const handleSubmit = (event) => { event.preventDefault(); Ajax(`words/${activeId}`, 'PUT', {word, description}, user.token) .then(res => { if(res.message === 'Token expired') { alert('Token expired. Please log in again.'); setUser({userId: null, token: null}); setAppState('login'); } else { alert('Word updated successfully'); setAppState('wordList'); } }) .catch(err => { alert('Error updating word:', err); }); };
削除機能の実装
削除も詳細画面作成時にボタンを配置していたため、APIを実装するだけです。
bodyData
がnull
であることに注意してください。
//src/components/pages/word/wordDetail.jsx > handleDelete const handleDelete = () => { if (window.confirm('Are you sure you want to delete this word?')) { Ajax(`words/${activeId}`, 'DELETE', null, user.token) .then(res => { if(res.message === 'Token expired') { alert('Token expired. Please log in again.'); setUser({userId: null, token: null}); setAppState('login'); } else { alert('Word deleted successfully'); setAppState('wordList'); } }) .catch(err => { alert('Error deleting word:', err); }); } };
まとめ
今回は、Reactアプリケーションにおける実践的なAPI連携について解説しました。
僕自身、API連携を学び始めた頃は難しく感じましたが、一つひとつの仕組みを理解すると、作れるものの幅がぐっと広がりプログラミングが楽しくなりました!
非同期処理のような複雑なテーマは、まず今回作成した関数のように使い方を理解し、それからなぜそう動くのかを内部のコードで理解していくと、知識が定着しやすいような気がします。今回の内容をベースに、ご自身のプロジェクトで試行錯誤してみてください!
最後までお読みいただき、ありがとうございました!