はじめに
ユーザーが最初に目にするログイン画面は、使いやすく魅力的なデザインであるべきです。
本記事では、Material UIを活用してモダンなログイン画面を作成する方法を紹介します。
スタイリッシュなUIを実装し、快適なログイン体験を提供しましょう。
ライブラリインストール
npm install @mui/material @emotion/react @emotion/styled
Login.js
rafce #ひな形作成 defalt export
Material UI ※Material UIのドキュメントと見ながら書くことができればOK
- https://mui.com/material-ui/getting-started/templates/
- https://github.com/mui/material-ui/blob/v6.4.5/docs/data/material/getting-started/templates/sign-in/SignIn.js
import React from 'react';
import { Container, Typography, Box, TextField, Button } from "@mui/material";
import { Link } from "react-router-dom";
const Login = () => {
return (
<>
<Container maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography variant='h5'>ログイン画面</Typography>
<TextField
sx={{
marginTop: 4,
}}
id="username"
type="username"
name="username"
placeholder="sekkeiya"
autoComplete="username"
autoFocus
required
fullWidth
variant="outlined"
/>
<TextField
sx={{
marginTop: 2,
}}
name="password"
placeholder="••••••"
type="password"
id="password"
autoComplete="current-password"
autoFocus
required
fullWidth
variant="outlined"
/>
<Button
sx={{
marginTop: 2,
}}
type="submit"
fullWidth
variant="contained"
>
Sign in
</Button>
<Link to="/register"
component="button"
type="button"
variant="body2"
sx={{ alignSelf: 'center' }}
>
新規登録はコチラ
</Link>
</Box>
</Container>
</>
)
}
export default Login
useStateを使ってuserオブジェクトを定義する
変数をセットしていく。一つのオブジェクトとして管理する。useStateというフックを使って更新を管理する
Login.js
import React,{useState} from 'react';
#省略
const Login = () => {
const [user, setUser] = useState({
username: "",
password: "",
});
handleChange関数定義(変わったときの動作)
const handleChange = (e)=>{
const {name,value} = e.target;
setUser({...user, [name]:value});
}
<TextField
#省略
onChange={handleChange}
/>
axiosを使ってjsonplaceholderのusers情報を取得する
https://axios-http.com/ja/docs/intro
npm install axios
Login.js
ログインボタンが押された時にaxiosを起動したい
<Button
#省略
onClick={onClickLogin} #ボタンを押された時にonClickLogin関数を実行
>
Sign in
</Button>
const onClickLogin = ()=>{
console.log("ログイン処理開始"); #ボタンを押したときにonClickLogin関数が呼び出されているかを確認
};
axiosを呼び出す処理を作成
axiosの使い方 ※axiosのドキュメントを見ながら書くことができればOK
jsonplaceholder:https://jsonplaceholder.typicode.com/users
import axios from "axios";
const onClickLogin = ()=>{
console.log("ログイン処理開始");
const endpoint = "https://jsonplaceholder.typicode.com/users"
axios
.get(endpoint)
.then(function (response) {
// 処理が成功した場合
console.log(response);
})
.catch(function (error) {
// エラー処理
console.log(error);
})
};
username入力欄に入力した名前がデータベースの情報と一致する場合にそのユーザーの情報を取得する→コンソールログで確認
const onClickLogin = ()=>{
console.log("ログイン処理開始");
const endpoint = "https://jsonplaceholder.typicode.com/users"
axios
.get(endpoint, {
params: {
username: user.username
}
})
.then(function (response) {
// 処理が成功した場合
console.log(response);
})
.catch(function (error) {
// エラー処理
console.log(error);
})
};
undefinedの時は「ログイン失敗」、それ以外は「ログイン成功」
const onClickLogin = ()=>{
console.log("ログイン処理開始");
const endpoint = "https://jsonplaceholder.typicode.com/users"
axios
.get(endpoint, {
params: {
username: user.username
}
})
.then(function (response) {
console.log(response.data[0]);
if(response.data[0] === undefined){
// 処理が失敗した場合
console.error("ログイン失敗");
}else{
// 処理が成功した場合
console.log("ログイン成功");
}
})
.catch(function (error) {
// エラー処理
console.error("ログイン失敗");
})
};
「ログイン成功」→ログイン成功画面へ、「ログイン失敗」→ログイン失敗画面へ
useNavigateを使って画面遷移する
import { Link, useNavigate } from "react-router-dom";
const Login = () => {
const navigate = useNavigate();
// 処理が失敗した場合
console.error("ログイン失敗");
navigate("/loginfailed");
// 処理が成功した場合
console.log("ログイン成功");
navigate("/");
passwordをログイン可否の判断に追加
idをpasswordとみなす
axios
.get(endpoint, {
params: {
username: user.username,
id:user.password #追記
}
})
ヘッダーの作成
Material UI ※Material UIのドキュメントと見ながら書くことができればOK
cd components
mkdir templates
cd templates
echo. > Header.js
以下はapp-barのほぼコピペ
import * as React from 'react';
import { AppBar, Box, Toolbar, Typography, Button, IconButton} from '@mui/material'; #整理
import MenuIcon from '@mui/icons-material/Menu';
export default function Header() {
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
HOME #HOMEに修正
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</Box>
);
}
Header.jsのHeader()関数をHome.jsにインポートして、ヘッダーを表示する
import React from 'react'
import Header from '../templates/Header' #Header関数をインポート
const Home = () => {
return (
<>
<Header /> #ヘッダーを表示
</>
)
}
export default Home

Menu Listを作成

cd components
mkdir elements
cd elements
echo. > BasicMenu.js
BasicMenu.js
Material UI ※Material UIのドキュメントと見ながら書くことができればOK
- アイコンをクリックしてサブメニューを表示したい
import * as React from 'react';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import MenuIcon from '@mui/icons-material/Menu';
import { IconButton} from '@mui/material'; #アイコンボタンをインポート
export default function BasicMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
#以下Header.jsのAppBarからコピペ
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
>
<MenuIcon onClick={handleClick}/> #handleClickを追記
</IconButton>
#<---ここまで--->
<Menu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
Header.js
import * as React from 'react';
import { AppBar, Box, Toolbar, Typography, Button } from '@mui/material';
import BasicMenu from '../elements/BasicMenu'; #追記 BasicMenu関数をインポート
export default function Header() {
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<BasicMenu /> #追記 BasicMenuを表示
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
HOME
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</Box>
);
}
BasicMenu.js
- サブメニューからそれぞれの画面に遷移させたい
navigateを使って画面遷移させる
useNavigateをインポート
import { useNavigate } from 'react-router-dom';
navigate を使うために useNavigate を定義する。
const navigate = useNavigate()
それぞれ関数を作成(handleClickHome、handleClickLogin)
const handleClickHome = () => {
navigate("/");
};
const handleClickLogin = () => {
navigate("/login");
};
ボタンを押された時にそれぞれの関数が実行される
<MenuItem onClick={handleClickHome}>ホーム</MenuItem>
<MenuItem onClick={handleClickLogin}>ログアウト</MenuItem>
異なるコンポーネント間でデータを共有する(演習)

①:useNavigateの第2引数にstateとしてデータを含めて、遷移させる
Login.js
axios
#省略
// 処理が成功した場合
console.log("ログイン成功");
navigate("/", { state: { username: "ABC" } }); #追記 navigateを使って"/"(Home)に遷移し、stateに username: "ABC" を渡す
}
})
Header..js
import * as React from 'react';
import { AppBar, Box, Toolbar, Typography, Button } from '@mui/material';
import BasicMenu from '../elements/BasicMenu';
import { useLocation } from 'react-router-dom' #追記 useLocationをインポート
import { useState } from 'react' #追記 useStateをインポート
export default function Header() {
const location = useLocation() #追記 useLocationを定義し、locationに格納
const [data,setData] = useState(location.state); #追記 useStateを任意の変数[data,setData]に定義
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<BasicMenu />
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
HOME : {data.username} #追記 dataのusernameを表示
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</Box>
);
}
Home.js
import React from 'react'
import Header from '../templates/Header'
const Home = () => {
return (
<>
<Header />
</>
)
}
export default Home
②:親コンポーネントから子コンポーネントへpropsとして渡す
親が何で子が何かが重要(どこからどこへ渡すか)
親をHome、子をHeaderとしてホーム画面のヘッダーにABCと表示する(親コンポーネントで子コンポーネントが使われている状態)
Home.js
import React from 'react'
import Header from '../templates/Header'
const Home = () => {
return (
<>
<Header name="ABC"/>
</>
)
}
export default Home
Header..js
import * as React from 'react';
import { AppBar, Box, Toolbar, Typography, Button } from '@mui/material';
import BasicMenu from '../elements/BasicMenu';
export default function Header(props) {
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<BasicMenu />
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Home : {props.name}
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</Box>
);
}
未ログイン状態であればログイン画面へ転送
LoginUserProvider.jsにグローバル変数を追加
LoginUserProvider.js
import React from 'react'
import { createContext, useState } from 'react'
export const LoginUserContext = createContext({});
export const LoginUserProvider = (props) => {
const {children} = props;
const [loginUser, setLoginUser] = useState("");
const [isLogined, setIsLogined] = useState(false); #追記 新しくisLoginedというグローバル変数を定義
return (
<LoginUserContext.Provider value={{loginUser, setLoginUser, isLogined, setIsLogined}}> #追記 isLogined, setIsLoginedを追加
{children}
</LoginUserContext.Provider>
)
}
Login.js
#省略
import { LoginUserContext } from '../providers/LoginUserProvider';
const Login = () => {
const { setLoginUser, setIsLogined } = useContext(LoginUserContext); #追記
#省略
// 処理が成功した場合
console.log("ログイン成功");
setLoginUser(response.data[0].username);
setIsLogined(true); #追記
navigate("/");
}
})
#省略
export default Login
Home.js
import React, { useContext } from 'react'
import Header from '../templates/Header'
import { Navigate, useLocation } from "react-router-dom"
import { useState } from "react"
import { LoginUserContext } from '../providers/LoginUserProvider'
const Home = () => {
const location = useLocation();
const [data, setData] = useState(location.state);
const { isLogined } = useContext(LoginUserContext);
if(!isLogined) {
return <Navigate to="/login" />;
} else {
return(
<>
<Header name="ABC"/>
{data.username}
</>
);
}
};
export default Home
Footerを作成
Footer.js
import React from 'react'
import { Typography } from '@mui/material';
function Copyright(props) {
return (
<Typography
variant="body2"
color="text.secondary"
align="center"
{...props}
>
{"Copyright ©"}
sekkeiya corp.
{new Date().getFullYear()}
{","}
</Typography>
);
}
const Footer = () => {
return (
<>
<Copyright sx={{mt:8, md:4}} />
</>
)
}
export default Footer;
App.js
#省略
import Footer from './components/templates/Footer'; #追記 Footerをインポート
function App() {
return (
<LoginUserProvider>
#省略
<Footer /> #追記 すべてのページに反映させる
</LoginUserProvider>
);
}
export default App;
カスタムフック
プログラムの見通しをよくする
ログイン機能を別で切り出す
useLogin.js
import React from 'react'
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from "axios";
import { LoginUserContext } from '../providers/LoginUserProvider';
export const useLogin = () => {
const { setLoginUser, setIsLogined } = useContext(LoginUserContext);
const navigate = useNavigate();
const login = (user) => {
console.log("ログイン処理開始");
const endpoint = "https://jsonplaceholder.typicode.com/users"
axios
.get(endpoint, {
params: {
username: user.username,
id:user.password
}
})
.then(function (response) {
console.log(response.data[0]);
if(response.data[0] === undefined){
// 処理が失敗した場合
console.error("ログイン失敗");
navigate("/loginfailed");
}else{
// 処理が成功した場合
console.log("ログイン成功");
setLoginUser(response.data[0].username);
setIsLogined(true);
navigate("/");
}
})
.catch(function (error) {
// エラー処理
console.error("ログイン失敗");
})
};
return {login}
}
Login.js
import React,{ useState} from 'react';
import { Container, Typography, Box, TextField, Button } from "@mui/material";
import { Link } from "react-router-dom";
import { useLogin } from '../hooks/useLogin'; #追記
const Login = () => {
const {login} = useLogin();
const [user, setUser] = useState({
username: "",
password: "",
});
const handleChange = (e)=>{
const {name,value} = e.target;
setUser({...user, [name]:value});
}
const onClickLogin = ()=>{
login(user) #追記
};
return (
<>
#省略
}
export default Login


