본문 바로가기
🔥React 뽀개기

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

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

주요 기능

  • 폼 POST 요청
  • 폼 입력값 유효성 + 오류 메시지 출력하기
  • 아이돌 검색
  • 등록 후 list 페이지로 이동

✅api GET으로 있는 아이돌 목록 띄우기

  1. 페이지사이즈를 크게해서 모든 데이터를 다 불러오기…
  // get: 리스트 보여주기
  const getIdolsData = async () => {
    setLoading(true);

    const result = await getIdols({ pageSize: 10000 });
    const idolsList = result.list;
    // 기존 아이돌 리스트 뒤에 연달아 나와야 함
    setIdolsData(idolsList );
  };

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

🌻폼 채우고 데이터 출력해보기

 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);
  };
  • 폼과 등록 버튼에는 onSubmit={handleSubmit}적용
  • 아이돌 아바타에 onClick 프롭 추가 후 해당 클릭한 아이돌 id 값 받아오기
  • 각 input에는 onChange={handleInputChange}
<IdolAvatar
 src={idol.profilePicture}
 size="medium"
 value={idol.id}
 onClick={() => {
  console.log(idol.id);
  setDatas((prevDatas) => ({
     ...prevDatas,
     idolId: idol.id,
   }));
 }}
/>

🌻아이돌 선택하면 체크 표시 해주기

{datas.idolId === idol.id ? (
                    <CheckedIdolAvatar
                      src={idol.profilePicture}
                      size="medium"
                      value={idol.id}
                    />
                  ) : (
                    <IdolAvatar
                      src={idol.profilePicture}
                      size="medium"
                      value={idol.id}
                      onClick={() => {
                        console.log(idol.id);
                        setDatas((prevDatas) => ({
                          ...prevDatas,
                          idolId: idol.id,
                        }));
                      }}
                    />
                  )}
  • 아이돌 아바타 선택하면 해당 아이디가 idolId에 저장된다. → idolId와 idol.id가 같으면 체크표시된 아바타로 선택됨.

✅api POST로 데이터 추가하기

넣어야 할 데이터

{
  "deadline": "2024-05-04T02:07:58.087Z", -> 날짜
  "targetDonation": 0, -> 필요한 크레딧
  "subtitle": "string", -> 내용
  "title": "string", -> 제목
  "idolId": 0 -> 아이돌 선택하면 해당 id 지정
}
  • requestData로 폼 데이터 가져오기
import axiosInstance from '../axiosInstance';

const postDonationsApi = async (requestData) => {
  try {
    await axiosInstance.post(`/donations`, requestData);
  } catch (error) {
    throw new Error(error.message);
  }
};

export default postDonationsApi;

 const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      console.log('폼 데이터:', datas);
      await postDonationsApi(datas);
      console.log('POST 요청 완료');
    } catch (error) {
      console.error('요청 에러:', error);
    }
  };

postDonationsApi(datas) - 폼 데이터 api로 보내기

✅폼 유효성에 따른 버튼 활성화 유무

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

    if (
      datas.idolId &&
      datas.title &&
      datas.subtitle &&
      datas.targetDonation &&
      datas.deadline
    ) {
      console.log('Form data:', datas);
      await postDonationsApi(datas);
    } else if (!datas.idolId) {
      alert('아이돌은 선택해주세요!');
    } else if (!datas.title) {
      titleRef.current.focus();
    } else if (!datas.subtitle) {
      subtitleRef.current.focus();
    } else if (!datas.deadline) {
      alert('마감일을 설정해주세요!');
    } else if (datas.targetDonation === 0) {
      alert('크레딧을 설정해주세요!');
    }
  };
  • useRef 써서 비어있으면 focus되도록 설정
  • 모든 값이 있어야 등록할 수 있음.

✅폼 유효성 재구현

폼 유효성에 관한 고찰…

const [datas, setDatas] = useState({
    idolId: '',
    title: '',
    subtitle: '',
    targetDonation: 0,
    deadline: '',
  });
// 비어있음 false, 값이 있으면 true
  // 초기상태는 모두 false
  const isIdolIdValid = !!datas.idolId;
  const isTitleValid = !!datas.title;
  const isSubtitleValid = !!datas.subtitle;
  const isDeadlineValid = !!datas.deadline;
  const isTargetDonationValid = datas.targetDonation !== 0;
  • 각 데이터 값이 비어있음 false, 값이 있으면 true로 설정하기
  • !!: 불리언 값으로 변환해줌.
{isIdolIdValid || (
              <p className="ml-2 mt-2 text-[12px] text-red-600">
                아이돌을 선택해주세요
              </p>
            )}
  • false면 p 오류태그 뜨게한다.

근데? 위의 방법처럼 하면 초기 페이지에서 바로 오류메시지가 떠버린다.

해결법) 각각 valid값을 state로 저장해서, 처음 렌더링될 때는 true값으로 오류메시지를 안 보이게 하고, 등록할 때 비어있으면 false로 상태 값을 바꿔서 오류메시지를 뜨게해보자..!

const [isIdolIdValid, setIsIdolIdValid] = useState(true);
  const [isTitleValid, setIsTitleValid] = useState(true);
  const [isSubtitleValid, setIsSubtitleValid] = useState(true);
  const [isDeadlineValid, setIsDeadlineValid] = useState(true);
  const [isTargetDonationValid, setIsTargetDonationValid] = useState(true);
  • 필요한 valid 값을 state로 저장하기.. 초기 상태는 true
 useEffect(() => {
    setIsIdolIdValid(true);
    setIsTitleValid(true);
    setIsSubtitleValid(true);
    setIsDeadlineValid(true);
    setIsTargetDonationValid(true);
  }, [datas]);
  • 초기 렌더링일 때 valid는 모두 true값으로 오류 메시지 안 보이게
  • datas가 실시간으로 바뀌면 오류메시지 안 뜨게
  const handleSubmit = async (e) => {
    e.preventDefault();
    if (
      datas.idolId &&
      datas.title &&
      datas.subtitle &&
      datas.targetDonation &&
      datas.deadline
    ) {
      await postDonationsApi(datas);
      alert('조공 등록이 완료되었습니다!');
      navigate('/list');
    } else {
      if (!datas.idolId) setIsIdolIdValid(false);
      if (!datas.title) setIsTitleValid(false);
      if (!datas.subtitle) setIsSubtitleValid(false);
      if (!datas.targetDonation) setIsDeadlineValid(false);
      if (!datas.deadline) setIsTargetDonationValid(false);
    }
  };
  • 등록 버튼을 눌렀을 때 인풋 값이 비어있으면 해당 인풋값 아래에 오류메시지 띄우기
  • 다 채워져 있으면 datas POST 요청으로 보내고 list 페이지로 이동

애니메이션 넣기

Animation - Tailwind CSS

 

Animation - Tailwind CSS

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'
      }
className={`font-regular w-[10rem] rounded-md bg-blackSecondary px-4 py-2 text-sm text-white focus:outline-none ${!isDeadlineValid ? 'animate-vibration border border-red-600' : ''}`}

✅아이돌 검색

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

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

    const result = await getIdols({ pageSize: 10000, keyword });
    const idolsList = result.list;
    // 기존 아이돌 리스트 뒤에 연달아 나와야 함
    setIdolsData(idolsList);
  };

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

const handleKeywordSearch = (e) => {
    setKeyword(e.target.value);
  };

<input
  placeholder="검색할 아이돌 이름 혹은 그룹명을 입력해주세요"
  onChange={handleKeywordSearch}
  className="border-whiteSecondary-500 font-regular h-[32px] w-full rounded-[3px] border bg-blackSecondary px-9 py-2 focus:outline-none"
/>
  • input에서 실시간으로 검색할 때마다 바로 아이돌 아바타가 뜨게 설정.

 

결과

 

 

 

 


⚠️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자 쿠키: 사용자가 방문한 웹사이트가 아닌 다른 웹사이트에서 설치하는 쿠키

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