Apps

012-Login+Graph|Step3_フロントエンド:Material UIを使ってログイン画面のデザインを作る

はじめに

ユーザーが最初に目にするログイン画面は、使いやすく魅力的なデザインであるべきです。

本記事では、Material UIを活用してモダンなログイン画面を作成する方法を紹介します。

スタイリッシュなUIを実装し、快適なログイン体験を提供しましょう。

ライブラリインストール

npm install @mui/material @emotion/react @emotion/styled

Login.js

rafce #ひな形作成 defalt export

Material UI ※Material UIのドキュメントと見ながら書くことができればOK

 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

-Apps