先前我們已經將登入和註冊頁開發完,今天要來進行導覽列的開發。
未登入
登入後
根據wireframe,我們將首頁和導覽列分別建立,因為導覽列在之後的頁面都會存在,是需要共用的元件。
我們先在layout
資料夾底下新增Header.js
,並進行切版和路由設定(點擊右上角的Sign In 時要能跳轉到Login頁面)
import { Link } from "react-router-dom";
const Header = (props) => {
return (
<header className="w-screen h-14 bg-black flex justify-between items-center px-8">
<Link to="/">
<div className="flex justify-between items-center">
{/* <img className="h-[24px]" src={logoImage} alt="logo" /> */}
<h3 className="ml-4 text-white">BLOG DEV</h3>
</div>
</Link>
<div className="text-gray-200">
<Link to="/posts">
<button className="px-8 py-2 hover:text-white">Posts</button>
</Link>
<Link to="/tags">
<button className="px-8 py-2 hover:text-white">Tags</button>
</Link>
<Link to="/login">
<button className="px-8 py-2 hover:text-white">Contact</button>
</Link>
</div>
<div className="flex items-center">
<Link to="/login">
<button className="text-gray-200 px-4 py-2 hover:text-white">
Sign In
</button>
</Link>
</div>
</header>
);
};
export default Header;
回到App.js
加上Header.js
的路由
//App.js
import Header from '../layout/Header;
import Login from './pages/Login';
import Register from './pages/Register';
import './App.css';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const router = createBrowserRouter([
{
path: '/',
element: <Header/>,
},
{
path: '/login',
element: <Login/>,
},
{
path: '/register',
element: <Register/>,
}
])
const App = () => {
return (
<RouterProvider router={router}/>
);
}
export default App;
上面只製作了未登入時的導覽列,若使用者是有登入時,是需要顯示發布文章的按鈕和他的預設頭像,但我們要怎麼判斷使用者是否登入了呢?
這時就需要使用React的Context
React Context API
是 React.js 提供的一個用於元件間共享值的機制。它可以讓你在不必透過 props 鏈(props chain)將值手動傳遞的情況下,將值共享給元件樹中的任何一個元件。React Context
因為react context
有以上特性,所以我們就能透過它來紀錄使用者是否登入的值
在contexts
資料夾底下建立AuthContext.js
檔案:
//AuthContext.js
import React, { createContext, useContext, useState } from 'react';
const AuthContext = createContext();
//自訂hook useAuth,用來回傳當前的Context值,,方便在其他元件中使用
export const useAuth = () => {
return useContext(AuthContext);
};
//透過AuthContext.Provider提供一個context的值給它的子元件。
//{ children }是它的子元件。透過value prop將認證狀態(isLoggedIn)和改變狀態的函數(setIsLoggedIn)傳遞給所有的子元件。
export const AuthProvider = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(!!localStorage.getItem('user'));
return (
<AuthContext.Provider value={{ isLoggedIn, setIsLoggedIn }}>
{children}
</AuthContext.Provider>
);
};
先回到App.js
,在原本的 <RouterProvider router={router}/>
外加上<AuthProvider><AuthProvider/>
,這樣我們才能存取到isLoggedIn
和setIsLoggedIn
。
import './App.css';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
import Login from './pages/Login';
import Register from './pages/Register';
import HomePage from './pages/Home';
const router = createBrowserRouter([
{
path: '/',
element: <HomePage />
},
{
path: '/login',
element: <Login/>,
},
{
path: '/register',
element: <Register/>,
}
])
const App = () => {
return (
<AuthProvider>
<RouterProvider router={router}/>
</AuthProvider>
);
}
export default App;
到Login.js
,我們需要在登入成功後將isLoggedIn
設為true
//Login.js
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext"; //引入useAuth
import api from "../api/api";
import Card from "../components/Card";
import useInput from "../hooks/useInput";
const LoginPage = (props) => {
const navigate = useNavigate();
//透過useAuth取得setIsLoggedIn的方法
const { setIsLoggedIn } = useAuth();
(略...)
const handleSubmit = (event) => {
if (!formIsValid) return;
event.preventDefault();
const userData = {
email,
password,
};
api
.post("/auth/login", userData)
.then((result) => {
localStorage.setItem("user", JSON.stringify(result));
//登入成功後要將isLoggedIn設為true
setIsLoggedIn(true);
navigate("/");
})
.catch((error) => {
setErrorMsg('Login failed. Your email or password is incorrect.')
console.error(error);
});
};
return (
<Card>
(略...)
</Card>
);
};
export default LoginPage;
回到Header.js
,引入useAuth
並加上是否登入的判斷,若已登入則顯示頭像和建立文章的按鈕
import { Fragment } from "react";
import { Link, useNavigate } from "react-router-dom";
//引入useAuth
import { useAuth } from "../../contexts/AuthContext";
import avatar from "../../assets/avatar.jpg";
const Header = (props) => {
return (
<header className="w-screen h-14 bg-black flex justify-between items-center px-8">
<Link to="/">
<div className="flex justify-between items-center">
{/* <img className="h-[24px]" src={logoImage} alt="logo" /> */}
<h3 className="ml-4 text-white">BLOG DEV</h3>
</div>
</Link>
<div className="text-gray-200">
<Link to="/posts">
<button className="px-8 py-2 hover:text-white">Posts</button>
</Link>
<Link to="/tags">
<button className="px-8 py-2 hover:text-white">Tag</button>
</Link>
</div>
<div className="flex items-center">\
{/* 判斷是否登入再顯示對應樣式 */}
{isLoggedIn ? (
<LoggedInComponents />
) : (
<Link to="/login">
<button className="text-gray-200 px-4 py-2 hover:text-white">
Sign In
</button>
</Link>
)}
</div>
</header>
);
};
export default Header;
//登入後的樣式
const LoggedInComponents = () => {
const navigate = useNavigate();
const handleSignOut = () => {
localStorage.removeItem("user");
navigate("/login");
};
return (
<Fragment>
<Link to="/edit-post/new">
<button className="flex items-center bg-violet-600 px-4 py-1 text-gray-100 hover:text-white hover:bg-violet-700 duration-150 mr-4 rounded">
<AiOutlinePlus />
<p className="ml-2">New Post</p>
</button>
</Link>
<div className="w-[32px] h-[32px] rounded-full overflow-hidden bg-gray-500 text-white">
User
</div>
</Fragment>
);
};
當我們在點擊頭像時,可以叫出功能列,我們使用headless UI來製作Menu。
功能列裡面有
//Header.js
import { Fragment } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useAuth } from "../../contexts/AuthContext";
import { AiOutlinePlus } from "react-icons/ai";
//引入headlessui
import { Menu, Transition } from "@headlessui/react";
import avatar from "../../assets/avatar.jpg";
const Header = (props) => {
(略...)
}
export default Header;
//登入後的樣式
const LoggedInComponents = () => {
const navigate = useNavigate();
//登出
const handleSignOut = () => {
//清除localstorage的使用者資訊,並導回登入頁
localStorage.removeItem("user");
navigate("/login");
};
return (
<Fragment>
<Link to="/edit-post/new">
<button className="flex items-center bg-violet-600 px-4 py-1 text-gray-100 hover:text-white hover:bg-violet-700 duration-150 mr-4 rounded">
<AiOutlinePlus />
<p className="ml-2">New Post</p>
</button>
</Link>
<Menu as="div" className="relative inline-block text-left">
<Menu.Button>
<div className="w-[32px] h-[32px] rounded-full overflow-hidden bg-gray-500 text-white">
User
</div>
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 mt-2 w-56 origin-top-right bg-white shadow-lg focus:outline-none flex flex-col rounded">
<Menu.Item as={Fragment}>
<a
href="/account-settings"
className="p-2 text-gray-800 bg-white hover:bg-neutral-100 text-left"
>
Account settings
</a>
</Menu.Item>
<Menu.Item as={Fragment}>
<button
className="p-2 text-gray-800 bg-white hover:bg-neutral-100 text-left"
onClick={handleSignOut}
>
Sign out
</button>
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
</Fragment>
);
};
Yihsuan 大你好,Header.js 裡的 <header> 的部分沒有 return,小提醒,感謝這系列文章,我繼續學習
感謝你的提醒!! 已修正