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,
});
};
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;
변경 상품 선택
가격 변경
데이터 전송
변경사항 확인
Vite를 사용할 때 API를 읽어오기 위한 환경 변수 설정
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));
}
};
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);
PocketBase API와 상호작용하는 가장 손쉬운 방법은 공식 클라이언트 SDK를 사용하는 것
SDK
Software Development Kit, 소프트웨어 개발 도구 모음.
즉, 어떤 소프트웨어를 만들기 위한 도구 모음
API는 SDK의 일부가 될 수 있다.
즉 , SDK 안에 간단한 구조로 라이브러리 모양의 응용 프로그램 프로그래밍 인터페이스(API)가 하나 혹은 여러개 들어가있을수도 있다.
API(Application Programming Interface)
프로그램의 기능을 다른 프로그램이 쓸 수 있게 하는 것
> pnpm add pocketbase
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;
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;