OpenAI API 프론트단에서 사용하기(2)

taeyooooon·2023년 4월 7일
0
post-thumbnail

0. 기획 & 목표

  • 입력한 색과 어울리는 색 3가지 추천
  • React Query를 이용한 로딩, 에러 처리
  • 다크모드, 다크모드 버튼 애니메이션
  • 추천 색 버튼 클릭 시 클립보드에 컬러코드 복사
  • 유지보수 & 재사용성을 고려한 코드짜기

1. React Query 데이터 패칭

React Query를 이용한 에러핸들링을 하던 중 에러가 React Query에서 에러가 포착되지 않고 connectGPT 로직 내부에서 에러가 포착되어서 확인해본 결과 아래처럼 error promise를 반환해 주거나 에러 핸들링을 react query의 onError 콜백을 사용했어야 했다

// ../api/connectGPT
try{
  // ...gpt post
} catch (error) {
    console.error(error);
    throw error;	// error 반환 추가
  }
import { ChangeEvent, FormEvent, useState } from 'react';
import { connectGPT } from '../api/connectGPT';
import { useMutation } from '@tanstack/react-query';
import { PacmanLoader } from 'react-spinners';

export default function Container() {
  const [inputValue, setInputValue] = useState<string>('');
  const [result, setResult] = useState<string>('');
  const { mutate, isLoading, isError } = useMutation(
    ['gpt', inputValue],
    () => connectGPT(inputValue),
    {
      onSuccess: (data) => {
        setResult(data);
      },
      onError: (error) => {
        console.error(error);
      },
    }
  );

  const colorCodeRegex = /#[A-Fa-f0-9]{6}\b/g;
  const colorCodes = result && result.match(colorCodeRegex);

  const splitResult = result && result.split('.');

  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    mutate();
  };

  const onChange = (e: ChangeEvent<HTMLInputElement>) => {
    setInputValue(e.target.value);
  };

  return (
    <div className='flex justify-center items-center h-screen '>
      <section className=' bg-primary dark:bg-primary-dark text-secondary dark:text-secondary-dark p-12 shadow-xl flex flex-col items-center rounded-lg overflow-hidden transition'>
        <h1 className=' text-4xl font-bold mb-8'>색조합 3가지 추천</h1>
        <form onSubmit={onSubmit}>
          <input
            type='text'
            placeholder='색깔을 입력하세요'
            value={inputValue}
            onChange={onChange}
            required
            className='border border-primary dark:border-primary-dark bg-secondary dark:bg-secondary-dark text-secondary dark:text-secondary-dark rounded-md p-2 text-center mb-6'
          />
        </form>

        {isLoading && (
          <PacmanLoader className=' mb-6' color='#ffee00' size={25} />
        )}

        {isError && (
          <div className=' text-red-500 font-semibold mb-6'>
            에러가 발생했습니다.
          </div>
        )}

        {splitResult && <GptResponse splitResult={splitResult} />}

        {colorCodes && <RecommendColor colorCodes={colorCodes} />}

        <DarkModeBtn />
      </section>
    </div>
  );
}

2. 다크모드 버튼 애니메이션

전에 사용해본 Context API 를 이용해 다크모드 상태관리를 하였고
velog처럼 버튼 클릭 시 애니메이션을 구현해보고 싶어서 진행해 보았다.

// /components/DarkModeBtn
import { useDarkMode } from '../context/DarkModeContext';
import { useTransition, animated } from '@react-spring/web';
import { HiMoon, HiSun } from 'react-icons/hi';

export default function DarkModeBtn() {
  const { darkMode, toggleDarkMode } = useDarkMode();

  const transitions = useTransition(darkMode, {
    initial: {
      transform: 'scale(1) rotate(0deg)',
      opacity: 1,
    },
    from: {
      transform: 'scale(0) rotate(-180deg)',
      opacity: 0,
    },
    enter: {
      transform: 'scale(1) rotate(0deg)',
      opacity: 1,
    },
    leave: {
      transform: 'scale(0) rotate(180deg)',
      opacity: 0,
    },

    reverse: true,
  });
  return (
    <>
      <button
        onClick={toggleDarkMode}
        className=' relative cursor-pointer text-3xl mt-4'
      >
        {transitions((style, item) => {
          return item ? (
            <div className='absolute top-1/2 -translate-x-1/2 -translate-y-1/2'>
              <animated.div style={style}>
                <HiSun />
              </animated.div>
            </div>
          ) : (
            <div className='absolute top-1/2 -translate-x-1/2 -translate-y-1/2'>
              <animated.div style={style}>
                <HiMoon />
              </animated.div>
            </div>
          );
        })}
      </button>
    </>
  );
}

그리고 유지보수가 편하게끔 다크모드에 따른 색상을 tailwind.config.js에 정의해두었다.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {
      backgroundColor: {
        primary: '#f8f9fa',
        'primary-dark': '#121212',
        secondary: '#ffffff',
        'secondary-dark': '#1E1E1E',
      },
      textColor: {
        primary: '#212529',
        'primary-dark': '#ececec',
        secondary: '#495057',
        'secondary-dark': '#D9D9D9',
      },
      borderColor: {
        primary: '#343A40',
        'primary-dark': '#E0E0E0',
      },
    },
  },
  darkMode: 'class',
  plugins: [],
};

3. 클립보드 복사

처음에는 아래 방식대로 작성을 했는데 클립보드 저장이 되기전에 alert가 실행되어서 확인해봤더니 성공할 경우 Promise를 반환하기 때문에 then 체이닝을 통해 alert를 실행 시켜줘야했다

// /utils/copyText.ts

// 정상 작동 X
export const copyText = (text: string) => {
  navigator.clipboard.writeText(text)
  alert('클립보드에 복사되었습니다.');
};
// /utils/copyText.ts

// 정상 작동 O
export const copyText = (text: string) => {
  navigator.clipboard
    .writeText(text)
    .then(() => alert('클립보드에 복사되었습니다.'));
};

https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText

4. 완성

Github - https://github.com/Taeyooooon/openai_toypj

참고한 글

https://tanstack.com/query/v4/docs/react/overview
https://velog.io/@velopert/velog-dark-mode

profile
응애🐣 프론트엔드

0개의 댓글