[프론트엔드 스쿨 6기] 🗓️ 8월22일

유동균·2023년 8월 22일
0

프론트엔드 스쿨 6기

목록 보기
41/44
post-thumbnail
post-custom-banner

1. PATCH

  • 권한 부여
  • Content-Type
  • 변경 내용
  • 변경 사항 확인

2. DELETE

  • 삭제 전 데이터
  • 삭제

  • 변경 사항 확인

3. CREATE

  • 생성 전 데이터
  • 생성

  • 변경 사항 확인

4. 수정 Form

  const titleId = useId();
  const priceId = useId();
  const colorId = useId();

  const { productId } = useParams();
  const { isLoading, data } = useProductItem(productId);

  const [formState, setFormState] = useState(initialFormState);

  useEffect(() => {
    if (!isLoading && data) {
      setFormState({
        title: data.title,
        price: data.price,
        color: data.color,
      });
    }
  }, [isLoading, data]);

  const handleChangeInput = ({ target }) => {
    setFormState({
      ...formState,
      [target.name]: target.value,
    });
  };

4.1 PATCH submit

import { useEffect, useId, useState } from 'react';
import { useParams } from 'react-router-dom';
import useProductItem from '@/hooks/useProductItem';
import Spinner from '@/components/Spinner';

const initialFormState = {
  title: '',
  color: '',
  price: 0,
};

function ProductEdit() {
  const titleId = useId();
  const priceId = useId();
  const colorId = useId();

  const { productId } = useParams();
  const { isLoading, data } = useProductItem(productId);

  const [formState, setFormState] = useState(initialFormState);

  useEffect(() => {
    if (!isLoading && data) {
      setFormState({
        title: data.title,
        price: data.price,
        color: data.color,
      });
    }
  }, [isLoading, data]);

  const handleChangeInput = ({ target }) => {
    setFormState({
      ...formState,
      [target.name]: target.value,
    });
  };

  const handleEditProduct = (e) => {
    e.preventDefault(); // 기본 이벤트 제거
    // console.log(formState); // 서버에 업데이트 요청할 데이터 (서버 전송 PATCH 요청)
    // Fetch API (직접 기술)

    // client => server(pocketbase)
    fetch(
      '${
        import.meta.env.VITE_PB_API
      }/collections/products/records/${productId}',
      {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(formState),
      }
    )
      .then((response) => console.log(response)) // ReadableStream => json() => HumanReadableData
      .catch((error) => console.log(error));
  };

  if (isLoading) {
    return <Spinner size={120} />;
  }

  if (data) {
    return (
      <>
        <h2 className="text-2xl text-center">
          {data.title}({data.color}) 수정 폼
        </h2>
        <form onSubmit={handleEditProduct}>
          {/* title */}
          <div>
            <label htmlFor={titleId}>타이틀</label>
            <input
              type="text"
              name="title"
              id={titleId}
              value={formState.title}
              onChange={handleChangeInput}
            />
          </div>
          {/* color */}
          <div>
            <label htmlFor={colorId}>color</label>
            <input
              type="text"
              name="color"
              id={colorId}
              value={formState.color}
              onChange={handleChangeInput}
            />
          </div>
          {/* price */}
          <div>
            <label htmlFor={priceId}>price</label>
            <input
              type="number"
              name="price"
              id={priceId}
              value={formState.price}
              onChange={handleChangeInput}
            />
          </div>
          <div>
            <button type="submit">Edit</button>
          </div>
        </form>
      </>
    );
  }
}

export default ProductEdit;
  • 변경 상품 선택

  • 가격 변경

  • 데이터 전송

  • 변경사항 확인

4.1.1 '${import.meta.env.VITE_PB_API}'

Vite를 사용할 때 API를 읽어오기 위한 환경 변수 설정

  • .gitignore 설정


4.2 DELETE submit

const handleDeleteProduct = () => {
  const userConfirm = confirm('정말로 지줄건가요?');
  if (userConfirm) {
    fetch(
      '${
        import.meta.env.VITE_PB_API
      }/collections/products/records/${productId}',
      { 
        method: 'DELETE' 
      }
    ).catch((error) => console.log(error));
  }
};
  • 삭제 후 페이지 이동 시키기
  • 삭제 요청


  • 변경 사항 확인

  • custom hook
const apiEndpoint = import.meta.env.VITE_PB_API;

export function useRead() {}

export function useCreate() {}

export function useUpdate() {
  return async function updateProduct(productId, productData) {
    return await fetch(
      '${apiEndpoint}/collections/products/records/${productId}',
      {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(productData),
      }
    );
  };
}

export function useDelete() {
  return async function deleteProduct(deleteId) {
    return await fetch(
      '${apiEndpoint}/collections/products/records/${deleteId}',
      {
        method: 'DELETE',
      }
    );
  };
}

  • 디바운스 유틸 함수
function debounce(callback, timeout = 300) {
  let cleanup;
  return (...args) => {
    clearTimeout(cleanup);
    cleanup = setTimeout(callback.bind(null, ...args), timeout);
  };
}

export default debounce;
  • 디바운스 사용
  const handleChangeInput = ({ target }) => {
    setFormState({
      ...formState,
      [target.name]: target.value,
    });
  };

  const handleDebounceChangeInput = debounce(({ target }) => {
    setFormState({
      ...formState,
      [target.name]: target.value,
    });
  }, 5000);
  • 디바운스 적용 전
  • 디바운스 적용 후

5. SDK PocketBase API와 상호작용

PocketBase API와 상호작용하는 가장 손쉬운 방법은 공식 클라이언트 SDK를 사용하는 것

SDK
Software Development Kit, 소프트웨어 개발 도구 모음.
즉, 어떤 소프트웨어를 만들기 위한 도구 모음
API는 SDK의 일부가 될 수 있다.
즉 , SDK 안에 간단한 구조로 라이브러리 모양의 응용 프로그램 프로그래밍 인터페이스(API)가 하나 혹은 여러개 들어가있을수도 있다.

API(Application Programming Interface)
프로그램의 기능을 다른 프로그램이 쓸 수 있게 하는 것

> pnpm add pocketbase

5.1 sign up

import { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import pb from '@/api/pocketbase';

function SignUp() {
  const navigate = useNavigate();

  const [formState, setFormState] = useState({
    name: '',
    username: '',
    email: '',
    password: '',
    passwordConfirm: '',
  });

  const handleRegister = async (e) => {
    e.preventDefault();
    const { password, passwordConfirm } = formState;

    if (password !== passwordConfirm) {
      alert('비밀번호가 일치하지 않습니다. 다시 확인해보세요.');
    }

    // PocketBase SDK 인증 요청
    await pb.collection('users').create({
      ...formState,
      emailVisibility: true,
    });

    navigate('/');
  };

  const handleInput = (e) => {
    const { name, value } = e.target;
    setFormState({
      ...formState,
      [name]: value,
    });
  };

  return (
    <div>
      <h2>회원가입</h2>

      <form
        onSubmit={handleRegister}
        className="flex flex-col gap-2 mt-2 justify-start items-start"
      >
        <div>
          <label htmlFor="name">사용자 이름</label>
          <input
            type="text"
            name="name"
            id="name"
            value={formState.name}
            onChange={handleInput}
            className="border border-slate-300 ml-2"
          />
        </div>
        <div>
          <label htmlFor="username">계정 이름</label>
          <input
            type="text"
            name="username"
            id="username"
            value={formState.username}
            onChange={handleInput}
            className="border border-slate-300 ml-2"
          />
        </div>
        <div>
          <label htmlFor="email">이메일</label>
          <input
            type="email"
            name="email"
            id="email"
            value={formState.email}
            onChange={handleInput}
            className="border border-slate-300 ml-2"
          />
        </div>
        <div>
          <label htmlFor="password">패스워드</label>
          <input
            type="password"
            name="password"
            id="password"
            value={formState.password}
            onChange={handleInput}
            className="border border-slate-300 ml-2"
          />
        </div>
        <div>
          <label htmlFor="passwordConfirm">패스워드 확인</label>
          <input
            type="password"
            name="passwordConfirm"
            id="passwordConfirm"
            value={formState.passwordConfirm}
            onChange={handleInput}
            className="border border-slate-300 ml-2"
          />
        </div>
        <div className="flex gap-2">
          <button type="submit" className="disabled:cursor-not-allowed">
            가입
          </button>
          <button type="reset">취소</button>
        </div>
      </form>

      <Link to="/signin">로그인</Link>
    </div>
  );
}

export default SignUp;

5.2 sign in

import { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import pb from '@/api/pocketbase';

function SignIn() {
  const navigate = useNavigate();

  const [formState, setFormState] = useState({
    email: '',
    password: '',
  });

  const handleSignIn = async (e) => {
    e.preventDefault();

    const { email, password } = formState;

    // PocketBase SDK 인증(로그인) 요청
    const authData = await pb
      .collection('users')
      .authWithPassword(email, password);

    console.log(authData);

    navigate('/');
  };

  const handleInput = (e) => {
    const { name, value } = e.target;
    setFormState({
      ...formState,
      [name]: value,
    });
  };

  return (
    <div>
      <h2>로그인 폼</h2>

      <form
        onSubmit={handleSignIn}
        className="flex flex-col gap-2 mt-2 justify-start items-start"
      >
        <div>
          <label htmlFor="email">이메일</label>
          <input
            type="email"
            name="email"
            id="email"
            value={formState.email}
            onChange={handleInput}
            className="border border-slate-300 ml-2"
          />
        </div>
        <div>
          <label htmlFor="password">패스워드</label>
          <input
            type="password"
            name="password"
            id="password"
            value={formState.password}
            onChange={handleInput}
            className="border border-slate-300 ml-2"
          />
        </div>
        <div className="flex gap-2">
          <button type="submit" className="disabled:cursor-not-allowed">
            로그인
          </button>
          <button type="reset">취소</button>
        </div>
      </form>

      <Link to="/signup">회원가입</Link>
    </div>
  );
}

export default SignIn;

post-custom-banner

0개의 댓글