-
Notifications
You must be signed in to change notification settings - Fork 5
핵심 hooks 설명
YUNHO edited this page Feb 27, 2023
·
1 revision
- View를 담당하는 컴포넌트에서 로직을 분리하기
- 반복되는 로직을 추상화해서 선언적으로 사용하기
핵심기능
- 추상화된 Form 및 Input 컨트롤러로 선언적인 Form 관리
- Input 관련 jsx부분에서 선언적인 에러 핸들링
- Input태그의 에러 체크 실행 타이밍 결정 가능
- Select, Text, CheckBox 타입의 Input 지원
세부 기능
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로 이뤄진 에러 객체
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;
핵심기능
- api요청과 관련된 로직을 선언적으로 사용할 수 있게하는 hooks
- AbortController를 활용한 React 컴포넌트 memory leak 에러 해결
- API 요청 이후 에러 핸들링 로직 포함
세부 기능
Name | Type | Description | Default |
---|---|---|---|
axiosInstance | Function | axios instance | x |
axiosConfig | Object | axios instance를 실행할 때 넘겨줄 params (params, data …) | x |
immediate | boolean | 컴포넌트가 렌더링되자마자 axiosInstance를 실행하는지 여부 (get요청일 때만 true) | true |
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}`);
}
}
핵심기능
- 무한스크롤 기능
Name | Type | Description | Default |
---|---|---|---|
callback | Function | 바라보고 있는 요소가 교차할 때(isIntersecting) 실행할 callback함수 | x |
customOption | Object | IntersectionObserver인스턴스를 생성하기 위한 option (root, rootMargin,threshold) | x |
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];
}
핵심기능
- 이미지 용 S3에 파일 업로드
- File 타입의 Input 컨트롤
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;
핵심기능
- 모달 및 드롭다운 모달을 위한 컨트롤러
Name | Type | Description | Default |
---|---|---|---|
initialMode | boolean | 최초에 드롭다운 모달을 보여주는지 여부 | false |
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;
유저정보를 관리하기 위한 로직이 있는 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이면 유저정보 삭제 후 로그인페이지로 이동
닉네임 중복 여부를 체크하는 custom hooks
Return : Object Type
- isNicknameDuplicate: db에 중복되는 닉네임이 있는지 여부
- isNickNameSameWithOrigin: 초기값과 새로 입력 받은 값이 같은지 여부
- onChangeCheckNicknameDuplicate : 새로 입력 받는 닉네임의 change핸들러
- onClickCheckDuplicateNickname : 입력받은 값이 중복되는지 확인 요청보내는 함수
기술스택 캐러셀 로직 애니메이션을 위한 custom hooks
윈도우 스크롤을 잠그는 custom hooks
setTimeout를 사용하기 위한 hooks
setInterval을 사용하기 위한 hooks
🏠 Home
- FE : MSW를 활용한 API Mocking
- FE : useRoutes로 라우팅 관리하기
- FE : UI/UX 용어 정리
- FE : Storybook 활용(feat. Chormatic을 활용한 협업)
- FE : 상태관리 migration (redux-→ react context api)
- FE : CORS란?(우리가 겪은 문제들)
- FE : 도메인별 Api 파일 구분 및 공통 에러 핸들러 생성
- FE : 사용자에게 API상태를 UI로 알려주자
- BE : java stream API 를 이용해 코드 가독성 제고
- BE : AOP를 활용해 특정 DTO에 정보 추가
- BE : DB 직접 조회를 줄이기 위한 노력