Skip to content

핵심 hooks 설명

YUNHO edited this page Feb 27, 2023 · 1 revision

custom hooks 목표

  • View를 담당하는 컴포넌트에서 로직을 분리하기
  • 반복되는 로직을 추상화해서 선언적으로 사용하기

useForm + validate function

핵심기능

  • 추상화된 Form 및 Input 컨트롤러로 선언적인 Form 관리
  • Input 관련 jsx부분에서 선언적인 에러 핸들링
  • Input태그의 에러 체크 실행 타이밍 결정 가능
  • Select, Text, CheckBox 타입의 Input 지원

세부 기능

Parameter: Object Type

Name Type Description Default
initialValues Object form에서 사용하는 input들의 객체 x
submitCallback Function form에서 사용할 submit 함수 x
validate Function form에서 사용할 Input들의 객체에 대한 validation을 체크하는 함수 x
mode “onChange”, “onSubmit” input 태그의 에러 체크를 언제할지 정하는 mode (onChange, onSubmit) onChange

validate function

Parameter: Object Type : input들의 key, value로 이뤄진 객체

Return: Object Type : input들의 key, value로 이뤄진 에러 객체

Return : Object Type

Name Type Description
inputValues Object form에서 사용하는 input들의 객체
validateError Object form에서 사용하는 input에 해당하는 에러 객체
onChangeHandler Function inputValues 중 모든 text Input OnChange를 위한 함수
onChangeHandlerWithSelect Function inputValues 중 모든 select 태그 OnChange를 위한 함수
submitHandler Function 모든 validate 조건을 만족하면 params의 submitCallback 실행
isTargetSatisfyValidate Function validation을 체크하고 싶은 input의 키 값을 넣으면 boolean을 반환
satisfyAllValidates boolean validateError 객체에 있는 모든 데이터의 value가 ""이거나 null이면 true를 반환
사용예시

More Detail Example : codesandbox-custom useForm with jsdoc

export const loginValidate = ({ email, password }) => {
  const validateErrors = {
    email: '',
    password: '',
  };
  if (!email) {
    validateErrors.email = '이메일이 입력되지 않았습니다. ';
  } else if (!email.match(emailRegex)) {
    validateErrors.email = '이메일 형식으로 입력해주세요.';
  }

  if (!password) {
    validateErrors.password = '비빌번호가 입력되지 않았습니다. ';
  } else if (password.length < 8 || password.length > 20) {
    validateErrors.password = '비빌번호는 8자 이상 20자 이하이어야 합니다. ';
  } else if (!password.match(passwordRegex)) {
    validateErrors.password =
      '숫자 한 개 이상, 특수문자 한 개 이상, 영어 한 개 이상, 포함 공백 불가';
  }

  return validateErrors;
};

export default function Login({ children }) {
  const { requestLogin } = useAuthService();

  const submitCallback = async (submitData) => {
    const parsedSubmitData = loginParser(submitData);
    requestLogin(parsedSubmitData);
  };

  const { inputValues, validateError, onChangeHandler, submitHandler, satisfyAllValidates } =
    useForm({
      initialValues: { email: '', password: '' },
      submitCallback,
      validate: loginValidate,
    });

  return (
    <S.Form onSubmit={submitHandler} id="loginForm">
      <TextInput
        name="email"
        type="email"
        placeholder="이메일"
        value={inputValues.email}
        onChange={onChangeHandler}
        isError={!!validateError.email}
        helperText={validateError.email}
      />
      <TextInput
        name="password"
        type="password"
        placeholder="비밀번호"
        value={inputValues.password}
        onChange={onChangeHandler}
        isError={!!validateError.password}
        helperText={validateError.password}
      />
      <Button
        theme="primary"
        type="submit"
        form="loginForm"
        disabled={!satisfyAllValidates}
        customStyle={S.SubmitButton}
      >
        Login
      </Button>
    </S.Form>
  );
}
코드
import { useToastNotificationAction } from 'contexts/ToastNotification';
import { notifyNewMessage } from 'contexts/ToastNotification/action';
import { TOAST_TYPE } from 'contexts/ToastNotification/type';
import { useState } from 'react';

const FORM_MODE = {
  onChange: 'onChange',
  onSubmit: 'onSubmit',
};

/**
 * form에서 사용할 Input들의 객체에 대한 validation을 체크하는 함수
 * @callback ValidateChecker  Callback for useForm validate
 * @param {Object} inputsObj  form에서 사용할 input들의 객체의 키 값들
 * @returns {Object} errorObj  Input들의 에러 객체들
 */

/**
 * useform이 동작하기 위해 외부에서 주입해야하는 params
 * @typedef {Object} userFormParams
 * @property {Object} initialValues  form에서 사용할 input들의 객체
 * @property {(inputValues: Object) => Promise<void>} submitCallback  form에서 사용할 submit 함수
 * @property {ValidateChecker} validate  form에서 사용할 Input들의 객체에 대한 validation을 체크하는 함수
 * @property {string} mode 체크를 언제할지 정하는 mode (onChange, onSubmit)
 */

/**
 * useForm을 사용하는 곳에서 사용할 method 및 state
 * @typedef {Object} userFormReturns
 * @property {Object} inputValues  form에서 사용하는 input들의 객체
 * @property {Object} validateError   form에서 사용하는 input에 해당하는 에러 객체
 * @property {(Event) => void} onChangeHandler  inputValues 중 모든 text Input OnChange를 위한 함수
 * @property {(Event) => void} onChangeHandlerWithSelect  inputValues 중 모든 select 태그 OnChange를 위한 함수
 * @property {(Event) => Promise<void>} submitHandler  모든 validate 조건을 만족하면 submitcallback 실행
 * @property {(target: string) => boolean} isTargetSatisfyValidate  validation을 체크하고 싶은 input의 키 값을 넣으면 boolean을 반환
 * @property {boolean} satisfyAllValidates  validateError 객체에 있는 모든 데이터의 value가 ""이거나 null이면 true를 반환
 */

/**
 * custom useForm hooks
 * @param {userFormParams} useFormParams useform이 동작하기 위해 외부에서 주입해야하는 params
 * @returns {userFormReturns} useForm을 사용하는 곳에서 사용할 method 및 값들
 */
const useForm = ({ initialValues, submitCallback, validate, mode = FORM_MODE.onChange }) => {
  const [inputValues, setInputValues] = useState(initialValues);
  const [validateError, setValidateError] = useState({});
  const notifyDispatch = useToastNotificationAction();

  const resetInputValues = () => {
    setInputValues(initialValues);
  };

  const resetValidateErrors = () => {
    setValidateError({});
  };

  /**
   * inputValues를 동시에 검사해서 모든 input에 에러가 없으면 true변환
   * @type {boolean}
   */
  const satisfyAllValidates = Object.values(validate(inputValues)).every((value) => !value);

  /**
   * validation을 체크하고 싶은 input의 키 값을 넣으면 boolean을 반환
   * @param {string} target validation을 체크하고 싶은 input의 키 값
   * @returns {boolean} 에러 메시지가 없으면 false 있으면 true
   */
  const isTargetSatisfyValidate = (target) => !!validateError[target];

  /**
   * 에러를 표시할 방법에 따라 에러객체를 반환하는 함수
   * @param {*} name input태그의 key값
   * @param {*} value  input태그의 값
   */
  const onChangeError = (name, value) => {
    if (mode === FORM_MODE.onChange) {
      const res = validate({ ...inputValues, [name]: value });
      setValidateError({ ...validateError, [name]: res[name] });
    } else {
      setValidateError(validate({ ...inputValues, [name]: value }));
    }
  };

  /**
   * inputValues 중 모든 text Input OnChange를 위한 함수
   * @param {event}
   * @return {void}
   */
  const onChangeHandler = (event) => {
    const { name, value } = event.target;
    setInputValues({ ...inputValues, [name]: value });
    onChangeError(name, value);
  };

  /**
   * inputValues 중 모든 select 태그 OnChange를 위한 함수
   * @param {event}
   * @return {void}
   */
  const onChangeHandlerWithSelect = ({ name, value }) => {
    setInputValues({ ...inputValues, [name]: value });
    onChangeError(name, value);
  };

  const showEntireError = () => {
    setValidateError(validate({ ...inputValues }));
    Object.values(validateError)
      .filter((error) => error)
      .forEach((error) => {
        notifyNewMessage(notifyDispatch, error, TOAST_TYPE.Error);
      });
  };

  /**
   * 모든 validate 조건을 만족하면 submitcallback 실행
   * @param {event}
   * @return {void}
   */
  const submitHandler = async (event) => {
    event && event.preventDefault();

    if (!satisfyAllValidates) {
      showEntireError();
      return;
    }
    await submitCallback(inputValues);
    resetInputValues();
    resetValidateErrors();
  };

  return {
    inputValues,
    validateError,
    onChangeHandler,
    onChangeHandlerWithSelect,
    submitHandler,
    satisfyAllValidates,
    isTargetSatisfyValidate,
  };
};

export default useForm;

useAxios

핵심기능

  • api요청과 관련된 로직을 선언적으로 사용할 수 있게하는 hooks
  • AbortController를 활용한 React 컴포넌트 memory leak 에러 해결
  • API 요청 이후 에러 핸들링 로직 포함

세부 기능

Parameter: Object Type

Name Type Description Default
axiosInstance Function axios instance x
axiosConfig Object axios instance를 실행할 때 넘겨줄 params (params, data …) x
immediate boolean 컴포넌트가 렌더링되자마자 axiosInstance를 실행하는지 여부 (get요청일 때만 true) true

Return : Object Type

Name Type Description
state Object isLoading, responseData, error로 구성
requestQuery Function GET API 새로운 axios config와 함께 api호출을 실행하는 함수
requestCommand Function GET API 이외의 새로운 axios config와 함께 api호출을 실행하는 함수
forceRefetch Function 기존 axios config로 같은 api호출을 실행하는 함수
resetState Function 새로운 axios config와 함께 api호출을 실행하는 함수
사용예시
const useComments = () => {
  // api관련 로직
  // Query 요청
  const {
    state,
    handleState,
    requestQuery: getCommentAPI,
    forceRefetch,
  } = useAxios({
    axiosInstance: commentApi.GET_COMMENT,
    axiosConfig: { postType, postId },
  });
  const { responseData: comments, isLoading, error: apiError } = state;

  // Command 요청
  const { requestCommand: postCommentApi } = useAxios({
    axiosInstance: commentApi.POST_COMMENT,
    immediate: false,
  });
  const { requestCommand: postReplyApi } = useAxios({
    axiosInstance: commentApi.POST_REPLY,
    immediate: false,
  });
  // 생략
};
코드
import { API_MESSAGE } from 'constant/api.constant';
import { ROUTE } from 'constant/route.constant';
import { useToastNotificationAction } from 'contexts/ToastNotification';
import { notifyNewMessage } from 'contexts/ToastNotification/action';
import { TOAST_TYPE } from 'contexts/ToastNotification/type';
import { useEffect, useReducer, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { deleteUserInfo } from 'service/auth';

/**
 * useAxios가 동작하기 위해 외부에서 주입해야하는 params
 * @typedef {Object} useAxiosParams
 * @property {(axiosConfig: Object) => Promise<any>} axiosInstance axios instance
 * @property {Object} axiosConfig axios instance를 실행할 때 넘겨줄 params
 * @property {boolean} immediate 컴포넌트가 렌더링되자마자 요청 보내는지 여부 (get요청일 때만 true)
 */

// return { state, requestQuery, requestCommand, handleExpiredToken, forceRefetch, resetState };

/**
 * useAxios를 사용하는 곳에서 사용할 method 및 state
 * @typedef {Object} useAxiosReturns
 * @property {Object} state isLoading, responseData, error로 구성
 * @property {Function} requestQuery GET API 새로운 axios config와 함께 api호출을 실행하는 함수
 * @property {Function} requestCommand GET API 이외의 새로운 axios config와 함께 api호출을 실행하는 함수
 * @property {Function} forceRefetch 기존 axios config로 같은 api호출을 실행하는 함수
 * @property {Function} resetState 새로운 axios config와 함께 api호출을 실행하는 함수
 */

/**
 * custom useAxios hooks
 * @param {useAxiosParams} useAxiosParams useAxios가 동작하기 위해 외부에서 주입해야하는 params
 * @returns {useAxiosReturns} useAxios를 사용하는 곳에서 사용할 method 및 state
 */
const useAxios = ({ axiosInstance, axiosConfig, immediate = true }) => {
  const notifyDispatch = useToastNotificationAction();
  const navigate = useNavigate();
  const [state, dispatch] = useReducer(reducer, {
    isLoading: true,
    responseData: null,
    error: null,
  });
  const [trigger, setTrigger] = useState(Date.now());
  const [controller, setController] = useState();

  /**
   * 기존 config값으로 api요청을 다시 요청
   * @function
   * @return {void}
   */
  const forceRefetch = () => {
    setTrigger(Date.now());
  };

  /**
   * state를 초기화
   * @function
   * @returns {void}
   */
  const resetState = () => {
    dispatch({ type: RESET_TYPE });
  };

  /**
   * local state를 수정 (useState와 유사하게 동작)
   * @function
   * @param {callback} callback useState의 내부 함수와 유사 useState(prev => prev)
   */
  const handleState = (callback) => {
    dispatch({ type: SET_STATE_TYPE, newState: callback(state.responseData) });
  };

  /**
   * httpStatus가 401,403이면 유저정보 삭제 후 로그인페이지로 이동
   * @function
   * @param {number} httpStatus
   * @returns {Promise<void>}
   */
  const handleExpiredToken = (httpStatus) => {
    if (httpStatus !== 403 && httpStatus !== 401) {
      return;
    }
    deleteUserInfo();
    notifyNewMessage(notifyDispatch, API_MESSAGE.EXPIRE_TOKEN, TOAST_TYPE.Info);
    setTimeout(() => {
      navigate(ROUTE.LOGIN);
    }, 2000);
  };

  /**
   * Query(조회) : HTTP GET 요청
   * @function
   * @param {Object} newConfig axios instance에 넘겨줄 새로운 axios config
   */
  const requestQuery = async (newConfig) => {
    dispatch({ type: LOADING_TYPE });
    try {
      const ctrl = new AbortController();
      setController(ctrl);
      const { data: responseData } = await axiosInstance({
        ...axiosConfig,
        ...newConfig,
        signal: ctrl.signal,
      });
      dispatch({ type: SUCCESS_TYPE, responseData });
    } catch (error) {
      console.error(error);
      handleExpiredToken(error.httpStatus);
      dispatch({
        type: ERROR_TYPE,
        error: {
          httpStatus: error.httpStatus,
          message: error.message,
        },
      });
    }
  };

  /**
   * Command(명렁) : HTTP POST, DELETE, PATCH ,etc... 요청
   * @function
   * @param {Object} commandParams command 요청에 필요한 매개변수
   * @param {Object} commandParams.newConfig axios instance에 넘겨줄 새로운 axios config
   * @param {string} commandParams.successMessage 토스트 알림으로 보여줄 성공 메시지(='요청 성공!')
   * @param {number} commandParams.seconds 토스트 알림을 몇초뒤에 표시할지(=1500)
   */
  const requestCommand = async ({ newConfig, successMessage = '요청 성공!', seconds = 1500 }) => {
    let isOverStandard = true;
    setTimeout(() => {
      if (isOverStandard) notifyNewMessage(notifyDispatch, API_MESSAGE.LOADING, TOAST_TYPE.Info);
    }, seconds);
    try {
      const ctrl = new AbortController();
      setController(ctrl);
      const response = await axiosInstance({
        ...axiosConfig,
        ...newConfig,
        signal: ctrl.signal,
      });
      // const message = response?.message;
      notifyNewMessage(notifyDispatch, successMessage, TOAST_TYPE.Success);
      return response;
    } catch (error) {
      handleExpiredToken(error.httpStatus);
      notifyNewMessage(notifyDispatch, error.message, TOAST_TYPE.Error);
    } finally {
      isOverStandard = false;
    }
    return null;
  };

  useEffect(() => {
    if (immediate) {
      requestQuery();
    }
    return () => controller && controller.abort();
  }, [trigger]);

  return { state, handleState, requestQuery, requestCommand, forceRefetch, resetState };
};

export default useAxios;

const LOADING_TYPE = 'LOADING';
const SUCCESS_TYPE = 'SUCCESS';
const ERROR_TYPE = 'ERROR';
const RESET_TYPE = 'RESET';
const SET_STATE_TYPE = 'SET_STATE';

function reducer(state, action) {
  switch (action.type) {
    case LOADING_TYPE:
      return {
        isLoading: true,
        responseData: null,
        error: null,
      };
    case SUCCESS_TYPE:
      return {
        isLoading: false,
        responseData: action.responseData,
        error: null,
      };
    case ERROR_TYPE:
      return {
        isLoading: false,
        responseData: null,
        error: action.error,
      };
    case RESET_TYPE:
      return {
        isLoading: false,
        responseData: null,
        error: null,
      };
    case SET_STATE_TYPE:
      return {
        isLoading: false,
        responseData: action.newState,
        error: null,
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

useIntersect

핵심기능

  • 무한스크롤 기능

Parameter

Name Type Description Default
callback Function 바라보고 있는 요소가 교차할 때(isIntersecting) 실행할 callback함수 x
customOption Object IntersectionObserver인스턴스를 생성하기 위한 option (root, rootMargin,threshold) x

Return : Array Type

Name Type Description
loadMoreRef Object root에 등록할 useRef 객체
page number 증가할 page 숫자
resetPage Function page를 0으로 만드는 함수
코드
import { useEffect, useRef } from 'react';

const defaultOption = {
  root: null,
  rootMargin: '0px',
  threshold: 0.1,
};

// FIXME: react hooks return 타입을 jsdoc로 만들 수 없다.. 추론이 안되네요. object로 변경 필요

/**
 * useIntersect를 사용하는 곳에서 사용할 method 및 state
 * @typedef useIntersectReturns
 * @type {Array}
 * @property {Object} loadMoreRef root에 등록할 useRef 객체
 * @property {number} page 증가할 page 숫자
 * @property {() => void} resetPage page를 0으로 만드는 함수
 */

/**
 * IntersectionObserver를 사용하기 위한 custom hooks
 * @param {callback} callback 바라보고 있는 요소가 교차할 때(isIntersecting) 실행할 callback함수
 * @param {Object} customOption  IntersectionObserver인스턴스를 생성하기 위한 option (root, rootMargin,threshold)  https://developer.mozilla.org/ko/docs/Web/API/IntersectionObserver/IntersectionObserver
 * @returns {useIntersectReturns}  useIntersect를 사용하는 곳에서 사용할 method 및 state
 */
export default function useIntersect(callback, customOption) {
  const loadMoreRef = useRef(null);

  const handleObserver = async ([entry], observer) => {
    if (entry.isIntersecting) {
      observer.unobserve(entry.target);
      await callback();
      observer.observe(entry.target);
    }
  };

  useEffect(() => {
    let observer;
    if (loadMoreRef.current) {
      observer = new IntersectionObserver(handleObserver, { ...defaultOption, ...customOption });
      observer.observe(loadMoreRef.current);
    }
    return () => observer && observer.current && observer.disconnect();
  }, [customOption, loadMoreRef]);
  return [loadMoreRef];
}

useFileUploader

핵심기능

  • 이미지 용 S3에 파일 업로드
  • File 타입의 Input 컨트롤

Parameter: No need

Return : Object Type

Name Type Description
imageFile string
onChangeFile Function setState
s3ImageId string S3에 업로드 된 이미지의 Id
s3ImageObj Object S3에 업로드 된 이미지 객체 {id: s3ImageId, path: /image/:s3ImageId}
uploadFileOnS3 Function s3에 이미지 업로드 요청하는 함수
deleteFileOnS3 Function s3에 올라간 이미지를 제거 요청하는 함수
코드
import { useToastNotificationAction } from 'contexts/ToastNotification';
import { notifyNewMessage } from 'contexts/ToastNotification/action';
import { useState } from 'react';
import etcApi from 'api/etc.api';
import { TOAST_TYPE } from 'contexts/ToastNotification/type';
import { useNavigate } from 'react-router-dom';
import { ROUTE } from 'constant/route.constant';
import useAuthService from './useAuthService';

/**
 * useFileUploader를 사용하는 곳에서 사용할 method 및 state
 * @typedef {Object} useFileUploaderReturns
 * @property {string} imageFile
 * @property {(Event) => void} onChangeFile
 * @property {string} s3ImageId s3ImageId S3에 업로드 된 이미지의 Id
 * @property {Object} s3ImageObj S3에 업로드 된 이미지 객체 {id: s3ImageId, path: /image/:s3ImageId}
 * @property {(submitImageFile: File) => Promise<{id: string, path: string} | null>} uploadFileOnS3 s3에 이미지 업로드 요청하는 함수
 * @property {() => Promise<void>} deleteFileOnS3  s3에 올라간 이미지를 제거 요청하는 함수
 */

/**
 * 파일 업로드 (s3)를 위한 cutom hooks
 * @returns {useFileUploaderReturns} useFileUploader를 사용하는 곳에서 사용할 method 및 state
 */
const useFileUploader = () => {
  const notifyDispatch = useToastNotificationAction();
  const navigate = useNavigate();
  const { handleExiredToken } = useAuthService();
  const [s3ImageId, setS3ImageId] = useState(null);
  const [s3ImageObj, setS3ImageObj] = useState(null);
  const [imageFile, setImageFile] = useState(null);

  /**
   * input[type="file"]을 변경을 위한 change handler
   * @param {Event}
   */
  const onChangeFile = (event) => {
    const targetImageFile = event.target.files[0];
    setImageFile(targetImageFile);
  };

  /**
   * s3에 이미지 업로드 요청하는 함수
   * @param {File} 새롭게 제출할 이미지 파일
   * @returns {Promise<void>}
   */
  const uploadFileOnS3 = async (submitImageFile) => {
    // 둘다 없을 때 실행하지 않는다.
    if (!imageFile && !submitImageFile) {
      return null;
    }
    try {
      const formData = new FormData();
      if (submitImageFile) {
        formData.append('file', submitImageFile);
      } else {
        formData.append('file', imageFile);
      }
      const response = await etcApi.uploadImage(formData);
      const {
        data: { id, path },
      } = response;
      setS3ImageId(id);
      setS3ImageObj({ id, path });
      return { id, path };
    } catch (apiError) {
      console.error(apiError);
      setS3ImageObj(null);
      handleExiredToken(apiError.httpStatus);
      notifyNewMessage(notifyDispatch, apiError.message, TOAST_TYPE.Error);
      return null;
    }
  };

  // FIXME: 아직 구현 안 됨. 백엔드랑 얘기하고 수정해야함.
  /**
   * s3에 올라간 이미지를 제거 요청하는 함수
   * @returns {Promise<void>}
   */
  const deleteFileOnS3 = async () => {
    try {
      const response = await etcApi.deleteImage();
      console.log('response :>> ', response);
    } catch (apiError) {
      console.error(apiError);
      notifyNewMessage(notifyDispatch, apiError.message, TOAST_TYPE.Error);
      navigate(ROUTE.ESSENTIAL_INFO.INDEX);
    }
  };

  return { imageFile, onChangeFile, s3ImageId, s3ImageObj, uploadFileOnS3, deleteFileOnS3 };
};

export default useFileUploader;

useDropdown

핵심기능

  • 모달 및 드롭다운 모달을 위한 컨트롤러

Parameter

Name Type Description Default
initialMode boolean 최초에 드롭다운 모달을 보여주는지 여부 false

Return : Array Type

Name Type Description
parent Object 부모에 등록할 useRef 객체
isDropdownOpen number 해당 dropdown 컴포넌트를 보여줄지 말지
shouldCloseDropdown Function 요소 밖을 클릭했을 때 dropdown 컴포넌트를 닫는 함수
openDropdown Function dropdown 컴포넌트를 여는 함수
closeDropdown Function dropdown 컴포넌트를 닫는 함수
코드
import { useState, useEffect, useRef } from 'react';

/**
 * useDropdown를 사용하는 곳에서 사용할 method 및 state
 * @typedef {Object} useDropdownReturns
 * @property {Object} parent 부모에 등록할 useRef 객체
 * @property {boolean} isDropdownOpen 해당 dropdown 컴포넌트를 보여줄지 말지
 * @property {(Event) => void} shouldCloseDropdown 요소 밖을 클릭했을 때 dropdown 컴포넌트를 닫는 함수
 * @property {() => void} openDropdown dropdown 컴포넌트를 여는 함수
 * @property {() => void} closeDropdown dropdown 컴포넌트를 닫는 함수
 */

/**
 * dropdown컴포넌트를 열고닫기 위한 hooks
 * @param {boolean} initialMode 최초에 드롭다운 모달을 보여주는지 여부
 * @returns {useDropdownReturns} useDropdown를 사용하는 곳에서 사용할 method 및 state
 */
const useDropdown = (initialMode = false) => {
  const parent = useRef();

  const [isDropdownOpen, setIsDropdownOpen] = useState(initialMode);

  const shouldCloseDropdown = (event) => {
    const isParentExistInComposedPath = event.composedPath().includes(parent.current);
    if (!isParentExistInComposedPath) {
      setIsDropdownOpen(false);
    }
  };

  const closeDropdown = () => {
    setIsDropdownOpen(false);
  };

  const openDropdown = () => {
    setIsDropdownOpen(true);
  };

  const handleClickdropdownTrigger = () => {
    isDropdownOpen ? closeDropdown() : openDropdown();
  };

  useEffect(() => {
    window.addEventListener('click', shouldCloseDropdown);

    return () => {
      window.removeEventListener('click', shouldCloseDropdown);
    };
  }, [parent]);

  return {
    parent,
    isDropdownOpen,
    shouldCloseDropdown,
    openDropdown,
    closeDropdown,
    handleClickdropdownTrigger,
  };
};

export default useDropdown;

기타

useAuthService.js

유저정보를 관리하기 위한 로직이 있는 custom hooks

Return : Object Type

  • handleUpdateUserInfo : 유저정보를 새롭게 요청하고 updateUserInfo service함수 실행
  • handleUpdateUserInfo : 유저정보를 새롭게 요청하고 updateUserInfo service함수 실행
  • handleDeleteUserInfo : deleteUserInfo service함수를 실행
  • requestSignUp : 회원가입 api 실행
  • requestLogin : 로그인 api 실행
  • requestLogout : 로그아웃 api 실행
  • saveJwtToken : jwt토근을 프론트에 저장
  • checkIsFirstLogin : 첫 로그인인지 확인하는 함수
  • handleExpiredToken: httpStatus가 401,403이면 유저정보 삭제 후 로그인페이지로 이동

useCheckUserDuplicate.js

닉네임 중복 여부를 체크하는 custom hooks

Return : Object Type

  • isNicknameDuplicate: db에 중복되는 닉네임이 있는지 여부
  • isNickNameSameWithOrigin: 초기값과 새로 입력 받은 값이 같은지 여부
  • onChangeCheckNicknameDuplicate : 새로 입력 받는 닉네임의 change핸들러
  • onClickCheckDuplicateNickname : 입력받은 값이 중복되는지 확인 요청보내는 함수

useSkillCarousel.js

기술스택 캐러셀 로직 애니메이션을 위한 custom hooks

useScrollLock.js

윈도우 스크롤을 잠그는 custom hooks

useSetTimeout.js

setTimeout를 사용하기 위한 hooks

useSetInterval.js

setInterval을 사용하기 위한 hooks

Clone this wiki locally