\ ポイント最大11倍! /

【React.js】Axios でエンドポイントアクセス用の API モジュールを作成しよう!

  • React.js で API エンドポイントにアクセスしてリソースを取得したい!

バックエンドサーバーにリソースを取得しにいく方法、最初のうちは結構戸惑うことも多いと思います。

この記事ではaxiosを使ってリソースを取ってくる方法をご紹介します。

React.js と Axios を使って API アクセスする方法

早速、具体的な方法をお伝えします。

  • APIアクセス用インスタンスを作成
  • 各エンドポイントにアクセスするコードを作成
  • ②を書くコンポーネントで呼び出す

それぞれ解説していきます。

エンドポイントへのアクセス用インスタンスを作成

API サーバーへのアクセスを担当するモジュールを作成しましょう。

ファイル名はutils/api.tsとして、axios.create()メソッドで API アクセス用のインスタンスを作ります。

import axios from 'axios';

const api = axios.create({
    baseURL: 'http://localhost:8000/api/',
    timeout: 1000,
});

export default api;

ここでエンドポイントの URL を定義しておくことで、エンドポイントが変更になったときはここだけ直せば良いことになります。

各エンドポイントにアクセスするためのモジュールを作成

上記のapi.tsで作成したインスタンスに対して、HTTP リクエストを実行するロジックを書いていきましょう。

import api from './api';

interface Matching {
    id?: number;
    [key: string]: any;  // 他のプロパティを含む可能性があるため
}

export const fetchMatchings = async (): Promise<Matching[]> => {
    try {
        const response = await api.get('matchings/');
        return response.data;
    } catch (error) {
        console.error('Error fetching matchings:', error);
        throw error;
    }
};

export const createMatching = async (matchingData: Matching): Promise<Matching> => {
    try {
        const response = await api.post('matchings/', matchingData);
        return response.data;
    } catch (error) {
        console.error('Error creating matching:', error);
        throw error;
    }
};

export const updateMatching = async (id: number, matchingData: Matching): Promise<Matching> => {
    try {
        const response = await api.put(`matchings/${id}/`, matchingData);
        return response.data;
    } catch (error) {
        console.error('Error updating matching:', error);
        throw error;
    }
};

export const deleteMatching = async (id: number): Promise<void> => {
    try {
        await api.delete(`matchings/${id}/`);
    } catch (error) {
        console.error('Error deleting matching:', error);
        throw error;
    }
};

このように、エンドポイントの種類ごとに一つのモジュールに集約すると便利です。

各コンポーネントで呼び出す

matchingService.tsで定義したAPIアクセスを各コンポーネントから呼び出しましょう。

少し長いですが、結論となるコードを掲載します。

import React, { useEffect, useState } from 'react';
import { fetchMatchings, createMatching, updateMatching, deleteMatching } from './matchingService';

interface Matching {
    id: number;
    name: string;
    // その他のプロパティを追加
}

const MatchingsList: React.FC = () => {
    const [matchings, setMatchings] = useState<Matching[]>([]);
    const [loading, setLoading] = useState<boolean>(true);
    const [error, setError] = useState<string | null>(null);

    useEffect(() => {
        const loadMatchings = async () => {
            try {
                const data = await fetchMatchings();
                setMatchings(data);
            } catch (error) {
                setError('Failed to fetch matchings');
            } finally {
                setLoading(false);
            }
        };

        loadMatchings();
    }, []);

    const handleCreate = async (newMatching: Matching) => {
        try {
            const createdMatching = await createMatching(newMatching);
            setMatchings([...matchings, createdMatching]);
        } catch (error) {
            console.error('Error creating matching:', error);
        }
    };

    const handleUpdate = async (id: number, updatedMatching: Matching) => {
        try {
            const updated = await updateMatching(id, updatedMatching);
            setMatchings(matchings.map(m => (m.id === id ? updated : m)));
        } catch (error) {
            console.error('Error updating matching:', error);
        }
    };

    const handleDelete = async (id: number) => {
        try {
            await deleteMatching(id);
            setMatchings(matchings.filter(m => m.id !== id));
        } catch (error) {
            console.error('Error deleting matching:', error);
        }
    };

    if (loading) return <div>Loading...</div>;
    if (error) return <div>{error}</div>;

    return (
        <div>
            <h1>Matchings List</h1>
            <ul>
                {matchings.map(matching => (
                    <li key={matching.id}>
                        {matching.name}
                        <button onClick={() => handleUpdate(matching.id, { ...matching, name: 'Updated Name' })}>Update</button>
                        <button onClick={() => handleDelete(matching.id)}>Delete</button>
                    </li>
                ))}
            </ul>
            <button onClick={() => handleCreate({ id: 0, name: 'New Matching' })}>Add New</button>
        </div>
    );
};

export default MatchingsList;

いきなりコードを見ても理解しづらいので、ステップに分けて詳細に解説していきます。

APIモジュールのインポート

すでに作成したmatchingService.tsをインポートします。

import { fetchMatchings, createMatching, updateMatching, deleteMatching } from './matchingService';

このコードでmatchingService.tsの中にある以下の関数が使えるようになりました。

  • fetchMatchings
  • createMatching
  • updateMatching
  • deleteMatching

データの取得

今回はコンポーネントが画面に描画されると同時に、API からデータ取得したいですよね。その場合には、画面表示というメインの機能の他に API アクセスといういわば「副作用」をつけたい。

このように副作用をつけたい場合にはuseEffectフックを利用します。(詳しくは後述

useEffect(() => {
    const loadMatchings = async () => {
        try {
            const data = await fetchMatchings();
            setMatchings(data);
        } catch (error) {
            setError('Failed to fetch matchings');
        } finally {
            setLoading(false);
        }
    };

    loadMatchings();
}, []);

useEffectの第二引数として空の配列[]を指定しているので、このコンポーネントが呼び出された瞬間に一度だけ API リクエストが実行されることになります。

データの一時保存場所

ここで、API から取得したデータを一時的に保存しておくためにsetMatchingsが必要になってきます。

それを定義しているのが以下のuseStateフックを用いた行です。(詳しくは後述)

const [matchings, setMatchings] = useState<Matching[]>([]);

ここでは簡単に説明します。matchingsはデータの一時保存場所を表し、setMatchingsは一時保存場所に格納するための関数になっています。

つまりmatchingsに値を格納する場合には、以下のように書きます。

setMatchings(data);

このコードは一つ前のuseEffectフック内で書いたコードでしたね。

API アクセスのためのコードを作り込んでいく

handleCreateでは、非同期関数を定義しています。新しいマッチングオブジェクトを作成して、現在のマッチングリストに追加する処理を行っています。

const handleCreate = async (newMatching: Matching) => {
    try {
        const createdMatching = await createMatching(newMatching);
        setMatchings([...matchings, createdMatching]);
    } catch (error) {
        console.error('Error creating matching:', error);
    }
};

ちょっと分解しつつ、解説してきます。

const handleCreate = async (newMatching: Matching) => {

まずはhandleCreate関数を宣言します。asyncとしているので非同期処理が行われることになります。

引数はMatching型のnewMatchingという引数を定義しました。

try {
    // ここに非同期処理を記述
} catch (error) {
    console.error('Error creating matching:', error);
}

tryブロック内で非同期処理を行って、エラーが発生したらcatchブロックで処理されることになります。

const createdMatching = await createMatching(newMatching);

先に定義しておいたcreateMatching関数を呼び出していますね。awaitを使っているので非同期処理で関数の結果が返ってくるまで待ち、結果が返ってきたらcreatedMatchingに格納しています。

setMatchings([...matchings, createdMatching]);

最後に状態の更新を行なっています。

setMatchingsは、useStateフックで定義された状態更新関数ですね。

...はスプレッド構文といって、現在のmatchings状態を一度展開しています。さらに新しく作成されたcreateMatchingをその末尾に追加した新しい配列を作成します。

これにより、matchings状態が更新されて、React がコンポーネントを際レンダリングしても新しいマッチングが表示されるようになります。

コードを理解するための補足情報

useState

useStateフックを使うとコンポーネント内で状態(state)を管理できるようになります。

const [matchings, setMatchings] = useState<Matching[]>([]);

上記の例ではmatchingsを状態変数と呼び、setMatchingsが状態を更新するための関数ということになります。

基本的な構文は次のとおりです。

const [state, setState] = useState(initialState);
  • state: 現在の状態の値
  • setState: 状態を更新するための関数
  • initialState: 状態の初期値

これを踏まえて、実際のコードを読み込んでいきましょう。

const [matchings, setMatchings] = useState<Matching[]>([]);

matchingsは状態変数、setMatchingsは状態を更新するための関数ですね。

問題は=以下のuseStateフック以降です。

まず()の中に空の配列が入っているので、初期値は[]となります。

残るは<Matching[]>の部分になります。

これは一言でいうと「Matching型の要素を持つ配列」であることを表していて、このような指定方法をジェネリクスと呼ばれます。

Matching型は、以下のように定義されます。

interface Matching {
    id?: number;
    [key: string]: any;  // 他のプロパティを含む可能性があるため
}

idnumber型であることが定義されています。さらに、?とあることからidはあってもなくても良い任意のプロパティであることがわかります。

[key: string]: anyは「インデックスシグネチャ」と呼ばれていて、任意の数のプロパティを持っていて、キーは文字列型、バリューはどんな型でも良いというanyが指定されています。

useEffectフックとは?

useEffectを利用すると、ページをロードした後の特定のタイミングで指定した処理を登録することができます。

このように画面描画以外に実行したい処理のことを「副作用(サイドエフェクト)」と呼びます。具体的には以下のような副作用が登録可能です。

  • API からデータを取得
  • タイマーを設定
  • DOM を直接操作

以下は、useEffectの基本的な使い方です。

import React, { useEffect } from 'react';

const ExampleComponent = () => {
    useEffect(() => {
        // ここに副作用を記述します。
        console.log('Component mounted or updated');

        // オプション: クリーンアップ関数を返す
        return () => {
            console.log('Component will unmount or update');
        };
    }, []); // 依存配列が空なので、この副作用はコンポーネントのマウント時にのみ実行されます。

    return <div>Example</div>;
};

この副作用を「いつ」発動させるかについては、useEffectの第二引数の配列(依存配列)で制御します。

次の二パターンを覚えておきましょう。

  • 空配列[]: 初回マウント時に一度だけ実行
  • 値入り配列[value1, value2]:値の変更のたびに実行

なお、マウントとは「コンポーネントが最初に画面表示される瞬間」のことで、さらに具体的には「コンポーネントが最初にDOMに挿入される瞬間」とも言えます。

以下は、useEffectを使ってコンポーネントがマウントされたときにデータを取得する例です。

import React, { useEffect, useState } from 'react';

const DataFetchingComponent = () => {
    const [data, setData] = useState(null);

    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch('https://api.example.com/data');
            const result = await response.json();
            setData(result);
        };

        fetchData();
    }, []); // 空の依存配列なので、この副作用はマウント時に一度だけ実行される

    if (!data) {
        return <div>Loading...</div>;
    }

    return (
        <div>
            <h1>Data</h1>
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
};

export default DataFetchingComponent;

この記事が気に入ったら
フォローしてね!

シェア・記事の保存はこちら!

この記事を書いた人

karo@プログラマのアバター karo@プログラマ プログラマ

「書くことで人の役にたつ」をモットーに活動中。
本職はプログラマで、Pythonが得意。
基本情報技術者試験合格。

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)