1
1

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】APIを使って名言を表示するアプリを作ってみた

Last updated at Posted at 2023-09-02

はじめに

ReactとJavaScriptの勉強を一通りできたので簡単なアプリを制作しようと思いました。

ですのでこの記事では、Reactを使用してシンプルな名言アプリを作成する手順を紹介します。名言アプリはQuotable APIを使用してランダムな名言を取得し、ユーザーに表示するアプリケーションです。

間違いがあればご指摘をお願いします。

今回作るものの完全イメージ

Videotogif (1).gif

新しい名言取得をクリックすると名言をランダムで取得ができ、テーマ切り替えをクリックすると文字の背景が変わります。

今回行うこと

  • コンポーネントの作成
  • useStateフックの使用
  • useEffectフックの使用
  • コンテキストの使用
  • APIデータの取得
  • 条件付きレンダリング
  • イベントハンドリング
  • CSSスタイリング
    …など

Reactの基礎を一通り使おうと思います。

目次

使用環境

OS:Windows10
react:18.2.0
npm :9.6.7
Node.js:18.17.1
api.quotable.io

ディレクトリ構造

- node_modules
- public
- src
   |- App.css
   |- App.js
   └─ components
        |- Quote.css
        |- Quote.js
        |- ThemeContext.js
-package-lock.json
-package-json 

srcフォルダの中は使用するファイルのみ記述しています。

Node.jsとnpmをインストールする

まずは公式ウェブサイト]:https://nodejs.org/ "にアクセスして推奨バージョンのNode.jsをダウンロードします。

プロジェクトのセットアップ

npx create-react-app quote-app
cd quote-app
npm start

npm startでアプリケーションを起動します。
React.png
この画面がでたら起動成功です!

コンポーネントの作成

アプリケーション内で使用するコンポーネントを作成しましょう。名言の表示と取得を担当する Quote.js コンポーネントを作成します。

components/Quote.js
import React, { useState, useEffect } from 'react';

// エラーメッセージを表示するコンポーネント
function ErrorComponent({ errorMessage }) {
  return <div className="error">{errorMessage}</div>;
}


function Quote() {
  // 名言の状態管理用のステート
  const [quotes, setQuotes] = useState([]);
  // ローディング状態のステート
  const [isLoading, setIsLoading] = useState(false);
  // エラーメッセージのステート
  const [error, setError] = useState(null);

// 名言を取得する非同期関数
const fetchQuotes = () => {
  // ローディング状態をtrueに設定
  setIsLoading(true);
  // エラーメッセージをクリア
  setError(null);
  // APIから名言を取得
  fetch('https://api.quotable.io/quotes?limit=500')
    .then(response => {
      if (!response.ok) {
        // レスポンスが成功でない場合はエラーをスロー
        throw new Error('Network response was not ok'); 
      }
      return response.json();
    })
    .then(data => {
      // 名言をステートにセット
      setQuotes(data.results);
      // ローディング状態をfalseに設定
      setIsLoading(false);
    })
    .catch(error => {
      // エラーメッセージをセット
      setError('名言の取得中にエラーが発生しました。');
      setIsLoading(false);
    });
};

  // コンポーネントがマウントされた際に名言を取得
  useEffect(() => {
    fetchQuotes();
  }, []);


  // 新しい名言を取得するボタンがクリックされたときの処理
  const handleFetchNewQuote = () => {
    const randomIndex = Math.floor(Math.random() * quotes.length);
    setQuotes(prevQuotes => {
      const newQuotes = [...prevQuotes];
      newQuotes[0] = quotes[randomIndex];
      return newQuotes;
    });
  };


  return (
    <div >
      <h2>名言一覧</h2>
      {error && <ErrorComponent errorMessage={error} />}
      {isLoading ? (
        <p>名言を取得中...</p>
      ) : (

        <div>
          <p>{quotes[0]?.content}</p>

          <button onClick={handleFetchNewQuote} disabled={isLoading}>
            {isLoading ? '取得中...' : '新しい名言を取得'}
          </button>
        </div>
      )}
    </div>
  );
}

export default Quote;

解説していきます。

Quote コンポーネントの作成

メインのアプリケーションコンポーネントである Quote コンポーネントを作成します。

//const [state, stateを更新する関数] = useState(初期値);
  const [quotes, setQuotes] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

ここはuseStateを使って、変数を宣言しています。
state: ステート変数(現在の状態を保持する変数)です。初期値はuseStateの引数として渡された値(初期値)で初期化されます。

setState: ステートを更新するための関数です。この関数を呼び出すことにより、ステートを新しい値に更新できます

fetchQuotes 関数の定義

const fetchQuotes = () => {
  // ローディング状態をtrueに設定
  setIsLoading(true);
  // エラーメッセージをクリア
  setError(null);
  // APIから名言を取得
  fetch('https://api.quotable.io/quotes?limit=500')
    .then(response => {
      if (!response.ok) {
        // レスポンスが成功でない場合はエラーをスロー
        throw new Error('Network response was not ok'); 
      }
      return response.json();
    })
    .then(data => {
      // 名言をステートにセット
      setQuotes(data.results);
      // ローディング状態をfalseに設定
      setIsLoading(false);
    })
    .catch(error => {
      // エラーメッセージをセット
      setError('名言の取得中にエラーが発生しました。');
      setIsLoading(false);
    });
};

APIから名言を取得します。この関数内でステートを更新し、ローディング状態とエラーメッセージを適切に処理します。

response はHTTPリクエストの結果を保持するオブジェクトです。

if (!response.ok) は、HTTP レスポンスの ok プロパティが false の場合を判定する条件式です。HTTP レスポンスの ok プロパティは、HTTP ステータスコードが成功を示すものであるかどうかを示すブール値です。

一般的に、HTTP ステータスコードが 200 から 299 の範囲にある場合、response.ok は true になります。これはリクエストが成功したことを示します。しかし、400 番台や 500 番台のステータスコードはエラーを示すものであり、response.ok は false になります。

したがって、!response.ok は「レスポンスが成功ではない場合」を意味します。具体的には、サーバーからのレスポンスが成功を示す HTTP ステータスコードではない場合、この条件が成り立ちます。

setIsLoading(false) は、Reactコンポーネント内でローディング状態を管理するためのコードです。このコードは以下の主な目的を持っています。

ローディング状態の初期化: コンポーネント内で非同期操作が始まる前に、setIsLoading(true) が呼び出され、ローディング状態が true に設定されます。これにより、ユーザーに対して「名言を取得中...」などのローディングメッセージを表示することができます。

ローディング状態の終了: 非同期操作が完了した後、成功時または失敗時に setIsLoading(false) が呼び出されます。これにより、ローディング状態が false に戻り、ローディングメッセージが非表示になります。

useEffectとは?

  useEffect(() => {
    fetchQuotes();
  }, []);

useEffect(() => { fetchQuotes(); }, []); は、Reactコンポーネント内で特定の効果(effect)を実行するためのコードです。このコードの主要な目的は、コンポーネントがマウントされたときに一度だけ fetchQuotes 関数を実行することです。

具体的な説明は以下の通りです:

  • useEffect フック: ReactのuseEffect フックは、コンポーネント内で副作用を実行するために使用されます。副作用は通常、データの取得、購読の設定、コンポーネントの更新などの操作を指します。

  • マウント時の実行: コードの中で、この useEffect フックは第2引数に空の配列([])を持っています。これは、この useEffect フックが初回のレンダリング時に一度だけ実行されることを示しています。つまり、コンポーネントがマウント(画面に表示)された直後に fetchQuotes 関数が呼び出されます。

  • fetchQuotes 関数の実行: fetchQuotes 関数は非同期関数で、APIから名言データを取得する役割を担っています。この関数はマウント時に呼び出され、名言データの取得が開始されます。

簡単に言えば、この useEffect フックはコンポーネントが最初に画面に表示されたときに名言データの取得をトリガーし、一度だけ実行されることを保証します。これにより、コンポーネントの初期化時に必要なデータを読み込むことができ、ユーザーに正確な情報を提供できるようになります。

handleFetchNewQuote 関数の定義

  const handleFetchNewQuote = () => {
    const randomIndex = Math.floor(Math.random() * quotes.length);
    setQuotes(prevQuotes => {
      const newQuotes = [...prevQuotes];
      newQuotes[0] = quotes[randomIndex];
      return newQuotes;
    });
  };

Reactアプリケーション内で新しい名言を取得し、表示するために handleFetchNewQuote 関数を使用します。

この関数の主な機能は次のとおりです:

randomIndex 変数は、quotes 配列内でランダムなインデックスを生成します。これにより、ランダムな名言が選択されます。

setQuotes 関数を使用して、新しい名言を表示するために quotes ステートを更新します。setQuotes の引数として、前回のステート(prevQuotes)を使用し、新しい名言が既存のリストの先頭に置かれるように設定します。

ボタンクリックで新しい名言を表示

この handleFetchNewQuote 関数を、ボタンクリックなどのイベントに関連付けて使用することで、ユーザーが新しい名言を表示できるようになります。具体的な実装は、Reactコンポーネント内でボタンを作成し、そのボタンがクリックされたときに handleFetchNewQuote を呼び出すことです。

<button onClick={handleFetchNewQuote}>新しい名言を表示</button>

UIの定義

return (
  <div>
    <h2>名言一覧</h2>
    {error && <ErrorComponent errorMessage={error} />}
    {isLoading ? (
      <p>名言を取得中...</p>
    ) : (
      <div>
        <p>{quotes[0]?.content}</p>
        <button onClick={handleFetchNewQuote} disabled={isLoading}>
          {isLoading ? '取得中...' : '新しい名言を取得'}
        </button>
      </div>
    )}
  </div>
);
  1. <h2>名言一覧</h2>: この要素は、ページのタイトルとして "名言一覧" を表示します。

  2. {error && <ErrorComponent errorMessage={error} />}: error ステートが存在する場合、<ErrorComponent> コンポーネントがエラーメッセージを表示します。エラーメッセージがない場合、この部分は表示されません。

  3. {isLoading ? ... : ...}: isLoading ステートの値に応じて表示内容を切り替えます。isLoadingtrue の場合、名言データの取得中であることを示す「名言を取得中...」のメッセージが表示されます。isLoadingfalse の場合、名言のコンテンツと新しい名言を取得するボタンが表示されます。

  4. <p>{quotes[0]?.content}</p>: quotes ステート内の最初の名言のコンテンツを表示します。?. 演算子は、quotes 配列が空でない場合にのみコンテンツを表示し、エラーを防ぎます。つまり、quotes 配列が空である場合でもエラーが発生しません。

  5. <button onClick={handleFetchNewQuote} disabled={isLoading}>...</button>: "新しい名言を取得" ボタンがあり、ボタンがクリックされたときに handleFetchNewQuote 関数が呼び出されます。isLoadingtrue の場合、ボタンはクリックできないように disabled 属性が設定されます。ボタンのラベルは、isLoading の状態に応じて "取得中..." または "新しい名言を取得" と切り替わります。

App.jsとApp.cssのファイルの中身を書き換える

App.js
/*App.js*/

import React from 'react';
import Quote from './components/Quote';
import './App.css';


function App() {
  return (
      <div className="app">
        <Quote />
      </div>
  );
}

export default App;
App.css
/* App.css */

body {
  font-family: 'Poppins', sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f7f7f7;
  color: #333333;
}

.app {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: linear-gradient(180deg, #00a8cc, #4d4d4d);
}

.quote {
  background-color: #ffffff;
  box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1);
  border-radius: 15px;
  padding: 30px;
  text-align: center;
  max-width: 400px;
  margin: 0 auto;
}


button {
  background-color: #00a8cc;
  color: #ffffff;
  border: none;
  border-radius: 25px;
  padding: 10px 30px;
  cursor: pointer;
  font-weight: bold;
  transition: background-color 0.3s ease-in-out;
}

button:hover {
  background-color: #007a9e;
}

.error {
  color: #e74c3c;
  font-size: 18px;
  margin-top: 15px;
}

p {
  font-size: 20px;
  margin: 30px 0;
}

ul {
  list-style: none;
  padding: 0;
}

li {
  font-size: 20px;
  margin: 10px 0;
}

そしたらこのような画面になります。
React2 (1).png

これでもいいっちゃいいのですが、微妙に見ずらいのでもう少し改良してみましょう。

テーマの管理

アプリケーション内でテーマを管理するために、ReactのcreateContext、useContext、そしてuseStateを活用して、シンプルなテーマ切り替え機能を実装します。

components/ThemeContext.js
import { createContext, useContext, useState } from 'react';


//新しいコンテキストを作成
const ThemeContext = createContext();

//コンテキストを使用するためのフックを定義
export function useTheme() {
  return useContext(ThemeContext);
}

//テーマの設定や切り替えを管理するプロバイダーコンポーネント
export function ThemeProvider({ children }) {
  //テーマの状態を管理するステート
  const [theme, setTheme] = useState('light');

  //テーマを切り替える関数
  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  const value = {
    theme,
    toggleTheme,
  };

//子コンポーネントをラップしてコンテキストを提供
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

Reactアプリケーションでテーマを切り替えるために、コンテキスト(Context)を使用する方法を学びましょう。この記事では、ReactのcreateContextuseContext、そしてuseStateを活用して、シンプルなテーマ切り替え機能を実装します。

1. 新しいコンテキストを作成する

最初に、テーマ情報を格納するための新しいコンテキストを作成します。

const ThemeContext = createContext();

2. テーマの設定や切り替えを管理するプロバイダーコンポーネントを作成

テーマ情報や切り替え関数を管理するためのプロバイダーコンポーネントを作成します。このコンポーネントは、テーマの状態を保持し、テーマを切り替えるための関数を提供します。

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  const value = {
    theme,
    toggleTheme,
  };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

3. テーマコンテキストを使用するカスタムフックを定義

テーマ情報を使用するためのカスタムフックuseThemeを定義します。

export function useTheme() {
  return useContext(ThemeContext);
}

4. テーマの切り替えを実装

テーマを切り替えるための関数toggleThemeを実装しました。この関数は、現在のテーマがlightならdarkに、darkならlightに切り替えます。

const toggleTheme = () => {
   setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

5.テーマ情報を提供し、テーマ切り替えを可能にするコンテキストを定義

return (
  <ThemeContext.Provider value={value}>
    {children}
  </ThemeContext.Provider>
);
  1. const value = {...}: valueオブジェクトは、コンテキストに提供される値を定義します。この場合、themetoggleThemeをプロパティとして持っています。これらのプロパティは、テーマの状態とテーマ切り替え関数を含んでいます。

  2. <ThemeContext.Provider value={value}>: ThemeContext.ProviderはReactのコンテキストプロバイダーコンポーネントです。このコンポーネントは、valueプロパティを使用してコンテキストの値を設定します。つまり、themetoggleThemeがこのコンテキスト内で利用可能になります。

  3. {children}: プロバイダーコンポーネントの中にある{children}は、ThemeProviderでラップされた子コンポーネントを指します。このパターンを使用することで、アプリケーション内のすべての子コンポーネントでテーマコンテキストを利用できるようになります。子コンポーネントはテーマ情報や切り替え関数をuseThemeフックを介して取得できます。

components/Quote.css
/* Quote.css */

.light {
  background-color: #ffffff;
  color: #333333;
}

.dark {
  background-color: #222222 !important;
  color: #ffffff;
}

.theme-button {
  background-color: transparent;
  color: #007bff;
  border: none;
  font-size: 14px;
  cursor: pointer;
}

.theme-button:hover {
  color: #0056b3;
}


Quote.cssを作成して上記のコードの貼り付け

App.js
import React from 'react';
import Quote from './components/Quote';
import { ThemeProvider } from './components/ThemeContext';//追加
import './App.css';

function App() {
  return (
    //テーマコンテキストをアプリ全体に適用
    <ThemeProvider>{/*追加*/}
      <div className="app">
        {/*名言を表示し、新しい名言を取得するコンポーネント*/}
        <Quote />
      </div>
    </ThemeProvider>//追加
  );
}

export default App;

components/Quote.js
import { useTheme } from './ThemeContext';
import './Quote.css'; 
import React, { useState, useEffect } from 'react';

function ErrorComponent({ errorMessage }) {
  return <div className="error">{errorMessage}</div>;
}


function Quote() {
  const [quotes, setQuotes] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  //テーマコンテキストからテーマとテーマを切り替える関数を取得
  const { theme, toggleTheme } = useTheme();//追加

const fetchQuotes = () => {
  setIsLoading(true);
  setError(null);
  fetch('https://api.quotable.io/quotes?limit=500')
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not ok'); 
      }
      return response.json();
    })
    .then(data => {
      setQuotes(data.results);
      setIsLoading(false);
    })
    .catch(error => {
      setError('名言の取得中にエラーが発生しました。');
      setIsLoading(false);
    });
};

  useEffect(() => {
    fetchQuotes();
  }, []);


  const handleFetchNewQuote = () => {
    const randomIndex = Math.floor(Math.random() * quotes.length);
    setQuotes(prevQuotes => {
      const newQuotes = [...prevQuotes];
      newQuotes[0] = quotes[randomIndex];
      return newQuotes;
    });
  };


  return (
    <div className={`quote ${theme}`}>{/*追加*/}
      <h2>名言一覧</h2>
      <button className="theme-button" onClick={toggleTheme}>
        テーマ切り替え
      </button>{/*追加*/}
      {error && <ErrorComponent errorMessage={error} />}
      {isLoading ? (
        <p>名言を取得中...</p>
      ) : (

        <div>
          <p>{quotes[0]?.content}</p>
          <button onClick={handleFetchNewQuote} disabled={isLoading}>
            {isLoading ? '取得中...' : '新しい名言を取得'}
          </button>
        </div>
      )}
    </div>
  );
}

export default Quote;

追加部分を書いたら完成です!
下記に使用したソースコードをまとめました。

ソースコード完全版

App.js
/*App.js*/

import React from 'react';
import Quote from './components/Quote';
import { ThemeProvider } from './components/ThemeContext';
import './App.css';

function App() {
  return (
    <ThemeProvider>
      <div className="app">
        <Quote />
      </div>
    </ThemeProvider>
  );
}

export default App;

src/components/Quote.js
// Quote.js

import React, { useState, useEffect } from 'react';
import { useTheme } from './ThemeContext';
import './Quote.css'; 

function ErrorComponent({ errorMessage }) {
  return <div className="error">{errorMessage}</div>;
}

function Quote() {
  const [quotes, setQuotes] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const { theme, toggleTheme } = useTheme();



const fetchQuotes = () => {
  setIsLoading(true);
  setError(null);
  fetch('https://api.quotable.io/quotes?limit=500')
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not ok'); 
      }
      return response.json();
    })
    .then(data => {
      setQuotes(data.results);
      setIsLoading(false);
    })
    .catch(error => {
      setError('名言の取得中にエラーが発生しました。');
      setIsLoading(false);
    });
};


  useEffect(() => {
    fetchQuotes();
  }, []);



  const handleFetchNewQuote = () => {
    const randomIndex = Math.floor(Math.random() * quotes.length);
    setQuotes(prevQuotes => {
      const newQuotes = [...prevQuotes];
      newQuotes[0] = quotes[randomIndex];
      return newQuotes;
    });
  };


  return (
    <div className={`quote ${theme}`}>
      <h2>名言一覧</h2>
      <button className="theme-button" onClick={toggleTheme}>
        テーマ切り替え
      </button>
      {error && <ErrorComponent errorMessage={error} />}
      {isLoading ? (
        <p>名言を取得中...</p>
      ) : (

        <div>
          <p>{quotes[0]?.content}</p>

          <button onClick={handleFetchNewQuote} disabled={isLoading}>
            {isLoading ? '取得中...' : '新しい名言を取得'}
          </button>
        </div>
      )}
    </div>
  );
}

export default Quote;

src/components/ThemeContext.js
/*ThemeContext.js*/

import { createContext, useContext, useState } from 'react';


const ThemeContext = createContext();

export function useTheme() {
  return useContext(ThemeContext);
}

export function ThemeProvider({ children }) {

  const [theme, setTheme] = useState('light');


  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  const value = {
    theme,
    toggleTheme,
  };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

App.css
/* App.css */

body {
  font-family: 'Poppins', sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f7f7f7;
  color: #333333;
}

.app {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: linear-gradient(180deg, #00a8cc, #4d4d4d);
}

.quote {
  background-color: #ffffff;
  box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1);
  border-radius: 15px;
  padding: 30px;
  text-align: center;
  max-width: 400px;
  margin: 0 auto;
}


button {
  background-color: #00a8cc;
  color: #ffffff;
  border: none;
  border-radius: 25px;
  padding: 10px 30px;
  cursor: pointer;
  font-weight: bold;
  transition: background-color 0.3s ease-in-out;
}

button:hover {
  background-color: #007a9e;
}

.error {
  color: #e74c3c;
  font-size: 18px;
  margin-top: 15px;
}

p {
  font-size: 20px;
  margin: 30px 0;
}

ul {
  list-style: none;
  padding: 0;
}

li {
  font-size: 20px;
  margin: 10px 0;
}

components/Quote.css
/* Quote.css */

.light {
  background-color: #ffffff;
  color: #333333;
}

.dark {
  background-color: #222222 !important;
  color: #ffffff;
}

.theme-button {
  background-color: transparent;
  color: #007bff;
  border: none;
  font-size: 14px;
  cursor: pointer;
}

.theme-button:hover {
  color: #0056b3;
}


おわりに

次はReact×TypeScriptで何か作ってみたいと思います。

参考サイト

React公式ドキュメント
https://react.dev/learn

https://kurarich.com/

1
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?