「Next.jsでつくるフルスタックアプリ後編」の感想・備忘録2

スポンサーリンク
「Next.jsでつくるフルスタックアプリ後編」の感想・備忘録1の続き

アイテムページ

編集ページの作成

  1. mkdir pages/item/update
  2. touch pages/item/update/[id].js
    pages/item/[id].jsは既に存在するのでpages/item/update/[id].jsとする。
import { useState } from 'react'

const UpdateItem = (props) => {
  const [title, setTitle] = useState(props.item.title);
  const [description, setDescription] = useState(props.item.description);
  const [price, setPrice] = useState(props.item.price);
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await fetch(`http://localhost:3000/api/item/update/${props.item._id}`, {
        method: 'POST',
        headers: {
          'Accrpt': 'application/json',
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('token')}`
        },
        body: JSON.stringify({
          title: title,
          description: description,
          price: price,
        })
      });
      const json = await response.json();
      alert(json.msg);
    } catch (err) {
      alert('更新失敗');
    }
  };
  return (
    <form onSubmit={handleSubmit}>
      <h1>アイテム作成</h1>
      <p>title: <input type="text" name="title" value={title} onChange={e=>setTitle(e.target.value)} required/></p>
      <p>description: <input type="text" name="description" value={description} onChange={e=>setDescription(e.target.value)}/></p>
      <p>price: <input type="text" name="price" value={price} onChange={e=>setPrice(e.target.value)}/></p>
      <p><button type="submit">保存</button></p>
    </form>
  )
};
export default UpdateItem;
export const getServerSideProps = async (context) => {
  const response = await fetch(`http://localhost:3000/api/item/${context.query.id}`);
  const item = await response.json();
  return {
    props: item
  }
};

ログイン状態をチェックするためのカスタムフック

カスタムフックの作成

  1. touch utils/useAuth.js
  • このように複数のコンポーネントで使う共通処理をuseXXXとして定義したものをカスタムフックと呼ぶ
  • jsonwebtoken9.0.0はブラウザではverifyでエラーとなるため、8.5.1へのダウングレードが必要
    npm remove jsonwebtoken
    npm install jsonwebtoken@8.5.1
  • トークンから取り出したemailを呼び出し元に返すためにステートを使う。
  • 呼び出された際はuseEffectが実行されていないので空文字がreturnされるが、その直後にuseEffectが実行されemailが更新されることで再レンダリングされる、
    ※ Reactではstateを更新すると再レンダリングが発生する
  • トークンがない・正しくない場合はuseRouter()のpushメソッドを使ってログインページにリダイレクトさせる
import {useState, useEffect} from 'react'
import {useRouter} from 'next/router'
import jwt from "jsonwebtoken";

const useAuth = () => {
  const [email, setEmail] = useState('');
  const router = useRouter();
  useEffect(() => {
    const token = localStorage.getItem('token');
    if (!token) {
      router.push('/user/login');
    }
    try {
      const decoded = jwt.verify(token, 'hogehogehoge');
      setEmail(decoded.email);
    } catch (err) {
      console.log(err)
      router.push('/user/login');
    }
  }, [router]);
  return email;
};
export default useAuth;

カスタムフックの使用

作成ページ
import { useState } from 'react'
import useAuth from "../../utils/useAuth"; // 追加

const CreateItem = () => {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [price, setPrice] = useState('');
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await fetch('http://localhost:3000/api/item/create', {
        method: 'POST',
        headers: {
          'Accrpt': 'application/json',
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('token')}`
        },
        body: JSON.stringify({
          title: title,
          description: description,
          price: price,
        })
      });
      const json = await response.json();
      alert(json.msg);
    } catch (err) {
      alert('登録失敗');
    }
  };
  const email = useAuth(); // 追加
  if (email) {
    return (
      <form onSubmit={handleSubmit}>
        <h1>アイテム作成</h1>
        <p>title: <input type="text" name="title" value={title} onChange={e => setTitle(e.target.value)} required/></p>
        <p>description: <input type="text" name="description" value={description}
                               onChange={e => setDescription(e.target.value)}/></p>
        <p>price: <input type="text" name="price" value={price} onChange={e => setPrice(e.target.value)}/></p>
        <p>
          <button type="submit">投稿</button>
        </p>
      </form>
    )
  }
};
export default CreateItem;
編集ページ
import { useState } from 'react'
import useAuth from "../../../utils/useAuth"; // 追加

const UpdateItem = (props) => {
  const [title, setTitle] = useState(props.item.title);
  const [description, setDescription] = useState(props.item.description);
  const [price, setPrice] = useState(props.item.price);
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await fetch(`http://localhost:3000/api/item/update/${props.item._id}`, {
        method: 'POST',
        headers: {
          'Accrpt': 'application/json',
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('token')}`
        },
        body: JSON.stringify({
          title: title,
          description: description,
          price: price,
        })
      });
      const json = await response.json();
      alert(json.msg);
    } catch (err) {
      alert('更新失敗');
    }
  };
  const email = useAuth(); // 追加
  if (email === props.item.email) { // 追加
    return (
      <form onSubmit={handleSubmit}>
        <h1>アイテム作成</h1>
        <p>title: <input type="text" name="title" value={title} onChange={e => setTitle(e.target.value)} required/></p>
        <p>description: <input type="text" name="description" value={description}
                               onChange={e => setDescription(e.target.value)}/></p>
        <p>price: <input type="text" name="price" value={price} onChange={e => setPrice(e.target.value)}/></p>
        <p>
          <button type="submit">保存</button>
        </p>
      </form>
    );
  }
};
export default UpdateItem;
export const getServerSideProps = async (context) => {
  const response = await fetch(`http://localhost:3000/api/item/${context.query.id}`);
  const item = await response.json();
  return {
    props: item
  }
};

コメント