본문 바로가기
✨FRONTEND/📍React

라이브러리 없이 state로 폼 만들기

by 짱돌보리 2024. 7. 31.
728x90

라이브러리 없이 state로 폼 만들기

주요 기능

  • 폼 입력 상태 관리 (useState)
  • 유효성 검사 + 오류 메시지
  • 아이돌 선택 (클릭 기반)
  • 아이돌 검색 (실시간)
  • API GET / POST
  • 등록 후 리스트 페이지 이동
  • 간단한 애니메이션 처리

1️⃣ 아이돌 리스트 가져오기 (GET)

아이돌 목록은 API로 받아온다. (그냥 한 번에 다 가져와서 처리하는 방식)

const getIdolsData = async () => {
  setLoading(true);

  const result = await getIdols({ pageSize: 10000 });
  const idolsList = result.list;

  setIdolsData(idolsList);
};

useEffect(() => {
  getIdolsData();
}, []);

2️⃣ 폼 상태 관리

const [datas, setDatas] = useState({
  idolId: '',
  title: '',
  subtitle: '',
  targetDonation: 0,
  deadline: '',
});
 const handleInputChange = (e) => {
    const { name, value } = e.target;

    if (name === 'targetDonation') {
      const val = e.target.value;
      setOutput(val);
      setMin(e.target.min || 0);
      setMax(e.target.max || 1000000);
    }

    setDatas((prevDatas) => ({
      ...prevDatas,
      [name]: value,
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    console.log(datas);
  };
  • 모든 input은 name 기반으로 상태 관리
  • 숫자 input은 따로 처리

3️⃣ 아이돌 선택 (클릭 기반)

클릭하면 idolId 저장됨

<IdolAvatar
  src={idol.profilePicture}
  size="medium"
  onClick={() => {
    setDatas((prev) => ({
      ...prev,
      idolId: idol.id,
    }));
  }}
/>

✔️ 선택된 아이돌 체크 표시

선택된 애만 UI 다르게 처리한다.

{datas.idolId === idol.id ? (
  <CheckedIdolAvatar src={idol.profilePicture} />
) : (
  <IdolAvatar
    src={idol.profilePicture}
    onClick={() => {
      setDatas((prev) => ({
        ...prev,
        idolId: idol.id,
      }));
    }}
  />
)}

4️⃣ 아이돌 검색 (실시간)

const [keyword, setKeyword] = useState('');

const handleKeywordSearch = (e) => {
  setKeyword(e.target.value);
};
useEffect(() => {
  getIdolsData();
}, [keyword]);

 

👉 입력할 때마다 API 다시 호출

<input
  placeholder="아이돌 검색"
  onChange={handleKeywordSearch}
/>

5️⃣ POST 요청 (폼 제출)

const postDonationsApi = async (requestData) => {
  await axiosInstance.post('/donations', requestData);
};

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

  try {
    await postDonationsApi(datas);
    alert('조공 등록 완료!');
    navigate('/list');
  } catch (error) {
    console.error(error);
  }
};

6️⃣ 유효성 검사 (1차 방식 → 문제 발생)

처음엔 이렇게 했었다.

const isTitleValid = !!datas.title;

{isTitleValid || <p>제목을 입력해주세요</p>}

 

❌ 문제 → 처음 들어오자마자 에러 메시지 다 뜸

7️⃣ 유효성 검사 개선

👉 valid 상태를 따로 관리

const [isTitleValid, setIsTitleValid] = useState(true);
  • 처음: true → 에러 안 보임
  • 제출 시: false로 바꿔서 에러 표시
const handleSubmit = async (e) => {
  e.preventDefault();

  if (
    datas.idolId &&
    datas.title &&
    datas.subtitle &&
    datas.targetDonation &&
    datas.deadline
  ) {
    await postDonationsApi(datas);
    navigate('/list');
  } else {
    if (!datas.idolId) setIsIdolIdValid(false);
    if (!datas.title) setIsTitleValid(false);
    if (!datas.subtitle) setIsSubtitleValid(false);
    if (!datas.deadline) setIsDeadlineValid(false);
    if (!datas.targetDonation) setIsTargetDonationValid(false);
  }
};

 

입력 바뀌면 에러 다시 숨김

useEffect(() => {
  setIsIdolIdValid(true);
  setIsTitleValid(true);
  setIsSubtitleValid(true);
  setIsDeadlineValid(true);
  setIsTargetDonationValid(true);
}, [datas]);

8️⃣ 에러 UI + 애니메이션

{!isDeadlineValid && (
  <p className="text-red-600">마감일을 입력해주세요</p>
)}

className={`input ${!isDeadlineValid ? 'animate-vibration border-red-600' : ''}`}

 

Tailwind 커스텀 애니메이션 (에러 시 살짝 흔들림)

Animation - Tailwind CSS

 

animation - Transitions & Animation

Utilities for animating elements with CSS animations.

tailwindcss.com

 

[CSS] 떨리는 효과 구현하기

 

[CSS] 떨리는 효과 구현하기

별도의 라이브러리 없이 간단한 코드로 진동처럼 흔들리는 애니메이션을 구현할 수 있습니다. 코드 See the Pen Vibration Animation Box by hyukson (@hyukson) on CodePen. 코드 풀이 떨림 효과를 줄 박스 요소를

gurtn.tistory.com

 

keyframes: {
  vibration: {
    from: { transform: 'rotate(0.5deg)' },
    to: { transform: 'rotate(-0.5deg)' },
  },
},
animation: {
  vibration: 'vibration .1s ease-in-out 3'
}

결과

9️⃣ CORS 이슈

웹 애플리케이션에서 다른 도메인으로 리소스를 요청할 때 발생할 수 있는 보안 정책

 

 

응답 헤더에 CORS 관련 설정 추가했다.

import axios from 'axios';

const teamName = '6-15';

const axiosConfig = {
  baseURL: `https://fandom-k-api.vercel.app/${teamName}`,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers':
      'Origin, X-Requested-With, Content-Type, Accept',
  },
};

const axiosInstance = axios.create(axiosConfig);

export default axiosInstance;
  • 모든 origin으로부터의 요청을 허용
  • 허용되는 HTTP 메서드 설정
  • 허용되는 헤더를 설정

⚠️서드파티 이슈

**Third-party cookie will be blocked. Learn more in the Issues tab.**

 

제3자 쿠키: 사용자가 방문한 웹사이트가 아닌 다른 웹사이트에서 설치하는 쿠키

 

 

이걸 해주면 된다는데 난 이미 되어있음 → 서드파티 쿠키 허용 사이트에 우리 사이트를 넣음