React ✖️ Unsplash APIで画像検索アプリを作る
はじめに
今回は、APIを叩く練習として、UnsplashというサイトのAPIを使った簡単な画像検索アプリを作ってみます!
完成品はこんな感じです。キーワードを入れて検索すると、関連する画像が取得されます。
githubのソースコードも適宜参照してください。
Unsplashとは
Unsplash は、世界中のフォトグラファーが提供する 高品質な写真を無料でダウンロード&利用できるサイト。無料で高画質な画像をサクッと手に入れられる!
しかも、APIを使えばプログラムで画像を検索・取得できるんです!
プロジェクトの作成
まずは任意のディレクトリでプロジェクトを作成しましょう。今回はviteを使いました。
npm create vite@latest unsplash-clone-app
cd unsplash-clone-app
npm install
code .
作成できたら、必要ない部分を消します。App.css``index.css
の中身は全て削除しちゃいましょう。
App.css
は、下のように書いておいてください。
body{
text-align: center;
}
App.jsx
を、簡単に書き換えておきます。
const App = () => {
return (
<div>
<h1>Hello</h1>
</div>
);
};
export default App;
サーバーを立ち上げて、「Hello」とだけ表示されていれば成功です!
npm run dev
APIキーの取得
Unsplash API を利用するために、開発者アカウントの登録をしましょう!
1. Unsplash Developers でキー取得
下記リンクから飛んでください
「Your App」を選択。
新しいアプリを作成するため、「New Applicarion」を選択します。
API の利用ガイドラインを確認したら、全てチェックして、「Accept terms」ボタンをクリック!
(画像では「Decline」が囲まれてますが、その右のボタンです)
アプリの名前と説明を入れてあげます。名前はわかりやすく「image-search-app」などにしておくといいでしょう。これで管理画面に遷移するはずです!
下の方に行くと、↑のように「Access Key」が確認できると思います。これをコピーしましょう!
開発時には、このキーを利用して実装していきます。
2. envファイルにキーを格納
vscodeに戻って、.env
ファイルを作成します。
今回のようなAPIキーは機密情報なので、基本的に非公開にしておきたいです。そこで、.env
ファイルに変数として記述しておいて、使う時にそこから呼び出す、という形式にします。
vite環境下では、変数名はVITE_から始めるというルールがあります。ここでは、以下のように書きました。
VITE_UNSPLASH_ACCESS_KEY=your_access_key
your_access_key
の部分を消して、先ほどコピーしたキーを貼り付けてあげてください。
これで、APIを使う準備ができました!
タイトルとフォームを作る
準備は全て完了したので、いよいよアプリの中身を実装していきましょう!
まずは、App.jsx
に、タイトルを記述します。
const App = () => {
return (
<div>
<h1>Unsplash Clone App</h1> /* 変更 */
</div>
);
};
export default App;
次に、検索するフォーム部分を作ります。ここはコンポーネントとして切り出すことにしました。
srcディレクトリの中に、componentsというディレクトリを新たに作ります。
この中に、Form.jsxというファイルを作成しましょう。
作ったファイルには、以下のように記述します。
const Form = () => {
return (
<div className="form-container">
<input
type="text"
name="keyword"
placeholder="写真とイラストを検索"
className="search-input"
/>
<button type="submit" className="search-button">
検索
</button>
</div>
);
};
export default Form;
App.jsxに戻って、今作ったコンポーネントをインポートしましょう
import Form from "./components/Form"; /* 追加 */
const App = () => {
return (
<div>
<h1>Unsplash Clone App</h1>
<Form /> /* 追加 */
</div>
);
};
export default App;
これで確認します。
タイトルとフォームの見た目はこれで完成です!しかし、この状態で入力しても、何も起こりません...検索ワードを管理する状態変数を用意して、入力欄で監視しましょう。
App.jsxで状態変数を定義します。
import React, { useState } from "react"; /* 追加 */
import Form from "./Form";
const App = () => {
const [word, setWord] = useState(""); /* 追加 */
return (
<div>
<h1>Unsplash Clone App</h1>
<Form />
</div>
);
};
export default App;
続いて、いま定義したsetWordを、Formコンポーネントに渡し、入力欄で監視できるように設定します。
import React, { useState } from "react";
import Form from "./Form";
const App = () => {
const [word, setWord] = useState("");
return (
<div>
<h1>Unsplash Clone App</h1>
<Form setWord={setWord} /> /* setWordを渡す */
</div>
);
};
export default App;
const Form = ({ setWord }) => { /* setWordを受けとる */
return (
<div className="form-container">
<input
type="text"
name="keyword"
placeholder="写真とイラストを検索"
onChange={(e) => setWord(e.target.value)} /* 入力内容の変化を監視 */
className="search-input"
/>
<button type="submit" className="search-button">
検索
</button>
</div>
);
};
export default Form;
きちんと設定できたのか、試しにwordを表示できるようにしてテストしましょう
import React, { useState } from "react";
import Form from "./Form";
const App = () => {
const [word, setWord] = useState("");
return (
<div>
<h1>Unsplash Clone App</h1>
<Form setWord={setWord} />
<div>{word}</div> /* 試しに追加 */
</div>
);
};
export default App;
これで確認してみます。入力欄に文字を打つと、その下に入力内容が反映されていればokです!
入力欄の変化が監視できるようになりました。
ちなみに、今追加したdivタグは確認用なので、消してもらって大丈夫です。
APIからデータを取得する
それでは、いよいよAPIから画像データを取得していきましょう!このアプリのキモの部分です!
App.jsxに、APIの非同期処理を追加しましょう。ここはほぼ雛形のようなものなので、丸々覚えちゃいましょう!
import React, { useState } from "react";
import Form from "./Form";
const App = () => {
const [word, setWord] = useState("");
const accessKey = import.meta.env.VITE_UNSPLASH_ACCESS_KEY; /* 追加 */
//APIからデータを取得する関数
const fetchImage = () => {
fetch(
`https://api.unsplash.com/search/photos?query=${word}&per_page=30&client_id=${accessKey}`
)
.then((res) => {
return res.json();
})
.then((data) => {
const results = data.results;
console.log(results);
});
};
return (
<div>
<h1>Unsplash Clone App</h1>
<Form setWord={setWord} />
<div>{word}</div>
</div>
);
};
export default App;
この部分は軽く解説します。
.env
に格納したアクセスキーは、上で呼び出しておきます。
const accessKey = import.meta.env.VITE_UNSPLASH_ACCESS_KEY;
fetch関数をつかって、APIから画像のデータを取得します。
fetch(
`https://api.unsplash.com/search/photos?query=${word}&client_id=${accessKey}`
)
.thenと繋げることで、fetch関数の処理が終わった後に行う処理を指定できます。これが「非同期処理」です。
ここでは、fetchで取得したデータをdataという変数に格納し、これをjson形式に変換します。
.then((res) => {
return res.json();
})
このjson形式のデータにはいろんなデータが含まれているので、さらにthenと繋げて、json形式のデータの中から画像のデータだけを取り出します。
.then((data) => {
const results = data.results;
console.log(results);
});
あとは、検索ボタンを押した時に画像データが取得されるようにしていきましょう!
App.jsxに、ボタンを押した時の処理を記述します。
その関数を、Formコンポーネントに渡して、ボタンから呼び出せるようにします。
import React, { useState } from "react";
import Form from "./Form";
const App = () => {
const [word, setWord] = useState("");
const accessKey = import.meta.env.VITE_UNSPLASH_ACCESS_KEY;
//APIからデータを取得する関数
const fetchImage = () => {
fetch(
`https://api.unsplash.com/search/photos?query=${word}&per_page=30&client_id=${accessKey}`
)
.then((res) => {
return res.json();
})
.then((data) => {
const results = data.results;
console.log(results);
});
};
//追加
//フォームの送信を処理する関数
const handleSubmit = (e) => {
if (!word) return; // wordが空の場合は何もしない
e.preventDefault();
fetchImage();
};
return (
<div>
<h1>Unsplash Clone App</h1>
<Form setWord={setWord} onSubmit={handleSubmit}/> /* 追加 */
<div>{word}</div>
</div>
);
};
export default App;
const Form = ({ setWord, onSubmit }) => { /* 受け取る */
return (
<div className="form-container">
<input
type="text"
name="keyword"
placeholder="写真とイラストを検索"
onChange={(e) => setWord(e.target.value)}
className="search-input"
/>
<button type="submit" onClick={onSubmit} className="search-button"> /* 追加 */
検索
</button>
</div>
);
};
export default Form;
これで確認してみましょう。入力欄になにかワードを入力して検索します。
consoleを開いて検索ボタンを押してみると、画像データが取得されているのが確認できると思います。
画像を表示する
それでは、取得した画像データから、実際に画像を表示させていきましょう!
App.jsxの中で、画像を管理する状態変数を定義して、画像データを格納します。
import React, { useState } from "react";
import Form from "./Form";
import Images from "./Images"; /* 追加 */
const App = () => {
const [word, setWord] = useState("");
const accessKey = import.meta.env.VITE_UNSPLASH_ACCESS_KEY;
const [images, setImages] = useState([]); /* 追加 */
//APIからデータを取得する関数
const fetchImage = () => {
fetch(
`https://api.unsplash.com/search/photos?query=${word}&per_page=30&client_id=${accessKey}`
)
.then((res) => {
return res.json();
})
.then((data) => {
const results = data.results;
console.log(results); /* 消してもok */
setImages(results); /* 追加 */
});
};
//フォームの送信を処理する関数
const handleSubmit = (e) => {
if (!word) return; // wordが空の場合は何もしない
e.preventDefault(); // フォームのデフォルトの送信を防ぐ
fetchImage();
};
return (
<div>
<h1>Unsplash Clone App</h1>
<Form setWord={setWord} onSubmit={handleSubmit} />
<Images images={images} /> /* 追加 */
</div>
);
};
export default App;
componentsフォルダの中にImages.jsxを作り、以下のように書きます。
const Images = ({ images }) => {
return (
<div className="image-container">
{photos.map((image, index) => (
<div key={index} className="image">
<img src={image.urls.small} alt="" />
</div>
))}
</div>
);
};
export default Images;
ここでは、App.jsxから画像データの入ったimagesを受け取り、map関数を使って展開しています。
これで、検索ボタンを押したら画像が表示されるはずです!
CSSを適用しよう
あとは、CSSをお好みで付けてあげてください!今回はこんな感じにしました。
body {
text-align: center;
}
.form-container {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
width: 100%;
max-width: 500px;
margin: 20px auto;
padding: 10px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
}
.search-input {
flex: 1;
padding: 12px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 8px;
outline: none;
transition: all 0.3s ease;
}
.search-input:focus {
border-color: #007bff;
box-shadow: 0px 0px 8px rgba(0, 123, 255, 0.3);
}
.search-button {
padding: 12px 20px;
font-size: 16px;
font-weight: bold;
color: white;
background: linear-gradient(45deg, #007bff, #0056b3);
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.search-button:hover {
background: linear-gradient(45deg, #0056b3, #003d80);
transform: scale(1.05);
}
.image {
display: flex;
align-items: center;
}
img {
width: 100%;
}
.image-container {
margin: 0 auto;
width: 80%;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
これにて完成です!!
Discussion