ReactでLINEログイン機能を実装する方法【コード付き解説】
【コード公開】LIFEアプリ作成手順|LINEログイン機能を実装する方法①の続きです。
ここからは、実際のコードを交えながら、Reactアプリケーションへのログイン機能の実装方法を詳しく解説します。フォルダ構成は前の記事を参考にしてください。
LIFFの初期化とログイン処理の記述
次に、LINEログインボタンとログイン処理を実装します。以下はシンプルなログインコンポーネントの例です。
LoginComponent.jsx
import { useEffect, useState } from 'react';
import liff from '@line/liff';
import { useNavigate } from 'react-router-dom';
function LoginComponent() {
const navigate = useNavigate();
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userProfile, setUserProfile] = useState(null);
const [error, setError] = useState("");
// LIFF初期化
useEffect(() => {
// 環境変数からLIFF IDを取得(.envファイルに設定)
const liffId = process.env.REACT_APP_LIFF_ID || import.meta.env.VITE_LIFF_ID;
const initializeLiff = async () => {
try {
await liff.init({ liffId });
console.log("LIFF初期化が完了しました");
// すでにログインしているかチェック
if (liff.isLoggedIn()) {
setIsLoggedIn(true);
fetchUserProfile();
}
} catch (e) {
setError(`LIFF初期化エラー: ${e.message}`);
console.error("LIFF初期化エラー", e.message);
}
};
initializeLiff();
}, []);
// ユーザープロフィール取得
const fetchUserProfile = async () => {
try {
const profile = await liff.getProfile();
setUserProfile(profile);
console.log("ユーザープロフィール取得成功:", profile);
// ログイン後にマイページなどへリダイレクト
// 少し遅延を入れるとスムーズに遷移します
setTimeout(() => {
navigate('/mypage', { state: { profile } });
}, 500);
} catch (e) {
setError(`プロフィール取得エラー: ${e.message}`);
console.error("プロフィール取得エラー", e.message);
}
};
// LINEログイン処理
const handleLogin = () => {
if (!liff.isLoggedIn()) {
// ログインしていなければ、ログイン処理を開始
liff.login();
}
};
// ログアウト処理
const handleLogout = () => {
liff.logout();
setIsLoggedIn(false);
setUserProfile(null);
console.log("ログアウトしました");
};
// エラーがあれば表示
if (error) {
return <div className="error-message">{error}</div>;
}
// ログイン済みの場合、ユーザー情報を表示
if (isLoggedIn && userProfile) {
return (
<div className="profile-container">
<h2>ログイン済み</h2>
<img
src={userProfile.pictureUrl}
alt="プロフィール画像"
className="profile-image"
/>
<p>名前: {userProfile.displayName}</p>
<button onClick={handleLogout} className="logout-button">
ログアウト
</button>
</div>
);
}
// 未ログインの場合、ログインボタンを表示
return (
<div className="login-container">
<h2>LINEでログイン</h2>
<button onClick={handleLogin} className="line-login-button">
LINEログイン
</button>
</div>
);
}
export default LoginComponent;
上記のコンポーネントは、以下の機能を持っています。
useEffect
内でLIFFの初期化を行い、すでにログインしているかをチェックします- ログインボタンのクリックで
liff.login()
を実行し、LINE認証画面を表示します - ログイン成功後、ユーザープロフィールを取得します
- ログイン状態に応じて、異なるUIを表示します
ログイン後の専用ページに遷移するルーティングの設定
ログイン後は専用ページ(マイページなど)に遷移させるのが一般的です。React Routerを使った実装例です。※importパスは実際のシステム構成に合わせて変更してください。
・App.jsx
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import liff from '@line/liff';
import LoginComponent from './components/LoginComponent';
import MyPage from './components/MyPage';
import Header from './components/Header';
import Footer from './components/Footer';
import './App.css';
function App() {
const [isLiffInitialized, setIsLiffInitialized] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
// 環境変数からLIFF IDを取得
const liffId = process.env.REACT_APP_LIFF_ID || import.meta.env.VITE_LIFF_ID;
// LIFF初期化
if (!isLiffInitialized) {
liff.init({ liffId })
.then(() => {
console.log('LIFF初期化完了');
setIsLiffInitialized(true);
// ログイン状態を更新
setIsLoggedIn(liff.isLoggedIn());
})
.catch((err) => {
console.error('LIFF初期化エラー:', err.message);
});
}
}, [isLiffInitialized]);
// ログイン状態が変わったときに検知する
useEffect(() => {
const checkLoginStatus = () => {
if (isLiffInitialized) {
setIsLoggedIn(liff.isLoggedIn());
}
};
window.addEventListener('focus', checkLoginStatus);
return () => window.removeEventListener('focus', checkLoginStatus);
}, [isLiffInitialized]);
// ログイン必須のルートに対するガード
const PrivateRoute = ({ children }) => {
return isLoggedIn ? children : <Navigate to="/" />;
};
return (
<Router>
<div className="app-container">
<Header isLoggedIn={isLoggedIn} />
<main className="main-content">
<Routes>
{/* ログインページ(ホーム) */}
<Route path="/" element={<LoginComponent />} />
{/* ログイン後のマイページ(要認証) */}
<Route
path="/mypage"
element={
<PrivateRoute>
<MyPage />
</PrivateRoute>
}
/>
{/* 他のルートを追加 */}
</Routes>
</main>
<Footer />
</div>
</Router>
);
}
export default App;
次に、マイページコンポーネントを作成します。このページはログイン後に表示され、ユーザー情報を活用して個別のコンテンツを提供できます。
・MyPage.jsx
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import liff from '@line/liff';
import './MyPage.css';
function MyPage() {
const navigate = useNavigate();
const [userProfile, setUserProfile] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// ログイン状態チェック
if (!liff.isLoggedIn()) {
navigate('/');
return;
}
// ユーザー情報取得
const fetchUserInfo = async () => {
try {
const profile = await liff.getProfile();
setUserProfile(profile);
setIsLoading(false);
// ここで必要に応じてバックエンドAPIにユーザー情報を送信
// saveUserInfoToBackend(profile);
} catch (error) {
console.error('プロフィール取得エラー:', error);
setIsLoading(false);
}
};
fetchUserInfo();
}, [navigate]);
// バックエンドAPIにユーザー情報を送信する関数(実装例)
const saveUserInfoToBackend = async (profile) => {
try {
// idTokenを取得(JWT形式のトークン)
const idToken = liff.getIDToken();
// バックエンドAPIにPOSTリクエスト
const response = await fetch('https://your-api.example.com/auth/line', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
idToken,
profile,
}),
});
if (response.ok) {
const data = await response.json();
console.log('バックエンド連携成功:', data);
// 必要に応じてセッション情報などをlocalStorageに保存
} else {
console.error('バックエンド連携エラー:', response.status);
}
} catch (error) {
console.error('APIエラー:', error);
}
};
// ログアウト処理
const handleLogout = () => {
liff.logout();
navigate('/');
};
if (isLoading) {
return <div className="loading">読み込み中...</div>;
}
return (
<div className="mypage-container">
<h1>マイページ</h1>
{userProfile && (
<div className="user-profile">
<div className="profile-header">
<img
src={userProfile.pictureUrl}
alt={userProfile.displayName}
className="profile-photo"
/>
<div className="profile-info">
<h2>{userProfile.displayName} さん</h2>
<p>ユーザーID: {userProfile.userId.substr(0, 8)}...</p>
<p>ステータスメッセージ: {userProfile.statusMessage || '設定なし'}</p>
</div>
</div>
<div className="content-section">
<h3>特典コンテンツ</h3>
<div className="content-cards">
<div className="content-card">
<h4>会員限定情報</h4>
<p>LINE会員のみ閲覧できるコンテンツです。</p>
</div>
<div className="content-card">
<h4>お得なクーポン</h4>
<p>LINE友だち限定のクーポンをご利用いただけます。</p>
</div>
</div>
</div>
<button onClick={handleLogout} className="logout-button">
ログアウト
</button>
</div>
)}
</div>
);
}
export default MyPage;
その他の最低限のReactコード一式
以下は、LINEログイン機能を実装するための最小限のコード構成です。これらのファイルを作成することで、基本的なLINEログイン機能を持つReactアプリケーションが完成します。
・環境変数ファイル(.env): LIFF IDなどの設定を保存します
# Create React Appの場合
REACT_APP_LIFF_ID=your-liff-id-here
# Viteの場合
VITE_LIFF_ID=your-liff-id-here
・CSSファイル(MyPage.css): マイページのスタイリングを定義します
.mypage-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.user-profile {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-top: 20px;
}
.profile-header {
display: flex;
align-items: center;
margin-bottom: 30px;
}
.profile-photo {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
margin-right: 20px;
}
.profile-info h2 {
margin: 0 0 10px;
font-size: 1.5rem;
color: #333;
}
.profile-info p {
margin: 5px 0;
color: #666;
}
.content-section {
margin: 30px 0;
}
.content-section h3 {
border-bottom: 2px solid #06C755;
padding-bottom: 10px;
margin-bottom: 20px;
color: #333;
}
.content-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.content-card {
background-color: #f9f9f9;
border-radius: 8px;
padding: 15px;
transition: transform 0.3s ease;
}
.content-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.content-card h4 {
color: #06C755;
margin-top: 0;
}
.logout-button {
background-color: #ff5252;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s;
margin-top: 20px;
}
.logout-button:hover {
background-color: #ff3232;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
font-size: 1.2rem;
color: #666;
}
・App.css: アプリケーション全体のスタイリングを定義します
.app-container {
font-family: 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
padding: 20px;
background-color: #f5f5f5;
}
/* ログインページスタイル */
.login-container {
max-width: 400px;
margin: 80px auto;
padding: 30px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.login-container h2 {
margin-bottom: 30px;
color: #333;
}
.line-login-button {
background-color: #06C755;
color: white;
border: none;
border-radius: 4px;
padding: 12px 24px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s;
width: 100%;
max-width: 300px;
}
.line-login-button:hover {
background-color: #05a849;
}
/* プロフィール表示スタイル */
.profile-container {
max-width: 400px;
margin: 40px auto;
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.profile-image {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
margin: 0 auto 20px;
}
/* エラーメッセージ */
.error-message {
color: #e74c3c;
background-color: #fde2e2;
padding: 15px;
border-radius: 4px;
margin: 20px auto;
max-width: 600px;
text-align: center;
}
・Header.jsx: ヘッダーコンポーネントを作成します
import { Link } from 'react-router-dom';
import liff from '@line/liff';
import './Header.css';
function Header({ isLoggedIn }) {
const handleLogout = () => {
if (isLoggedIn) {
liff.logout();
window.location.href = '/';
}
};
return (
<header className="header">
<div className="header-container">
<Link to="/" className="logo">
LINE Login Demo
</Link>
<nav className="nav">
<ul className="nav-list">
<li>
<Link to="/" className="nav-link">ホーム</Link>
</li>
{isLoggedIn ? (
<>
<li>
<Link to="/mypage" className="nav-link">マイページ</Link>
</li>
<li>
<button onClick={handleLogout} className="nav-button logout">
ログアウト
</button>
</li>
</>
) : (
<li>
<Link to="/" className="nav-button login">
ログイン
</Link>
</li>
)}
</ul>
</nav>
</div>
</header>
);
}
export default Header;
・Footer.jsx: フッターコンポーネントを作成します
import './Footer.css';
function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className="footer">
<div className="footer-container">
<p>© {currentYear} LINE Login Demo App. All rights reserved.</p>
<div className="footer-links">
<a href="https://developers.line.biz/ja/docs/liff/" target="_blank" rel="noopener noreferrer">
LIFF Documentation
</a>
<a href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
React
</a>
</div>
</div>
</footer>
);
}
export default Footer;