우리가 사용한 Custom Hook - boostcamp-2020/Project15-C-Client-Based-Formula-Editor GitHub Wiki
import { useCallback, useState } from 'react';
const useToggle = (
initialValue: boolean
): [boolean, () => void, React.Dispatch<React.SetStateAction<boolean>>] => {
const [value, setValue] = useState(initialValue);
const onToggle = useCallback(() => {
setValue((value) => !value);
}, []);
return [value, onToggle, setValue];
};
export default useToggle;
useToggle은 가장 간단한 custom hook으로 value가 true였으면 false로 false였으면 true로 반전 시키는 hook이다. 반환값으로 value state와 state를 toggle을 시키는 onToggle 함수를 반환하고, 따로 setValue가 필요하면 사용할 수 있도록 setValue를 return 해주었다.
onToggle 함수는 boolean 값을 반전만 시키면 되기 때문에 함수를 다시 생성하지 않도록 useCallback으로 감싸서 최적화 시켜주었다.
import { useState, useCallback } from 'react';
const useSelect = (
initialValue: string
): [
string,
(event: React.ChangeEvent<HTMLSelectElement>) => void,
React.Dispatch<React.SetStateAction<string>>
] => {
const [value, setValue] = useState(initialValue);
const onChange = useCallback((event) => {
setValue(event.target.value);
}, []);
return [value, onChange, setValue];
};
export default useSelect;
useSelect는 select element의 onChange 함수에 적용하기 위한 hook이다. initialValue를 인자로 받아서 value, onChange, setValue를 return한다. onChange는 event를 인자로 받아서 event.target.value의 값을 state로 바꿔주는 함수이다. select의 onChange에적용할 경우 option element의 value값이 넘어온다. 언제나 클릭된 element의 value만 쓰이기 때문에 함수가 한번만 선언 되면 된다. 따라서 useCallback으로 최적화를 해주었다.
import { useState, useCallback } from 'react';
const useInput = (
initialValue: string
): [string, (event: React.ChangeEvent<HTMLInputElement>) => void, () => void] => {
const [input, setInput] = useState(initialValue);
const onChange = useCallback((event) => {
setInput(event.target.value);
}, []);
const clearInput = useCallback((): void => {
setInput('');
}, []);
return [input, onChange, clearInput];
};
export default useInput;
useInput 또한 useSelect와 비슷한 함수이다. 차이점은 input State의 값을 초기화 시켜주는 함수를 만들어 반환해준다는 것이다. useInput 함수를 통해 여러 검색 창의 onChange 함수를 만들지 않고 초기값만 넣어주도록 만들어 편리하게 개발 할 수 있었다.
import { RootState } from '@contexts/index';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
function useCurrentTab() {
const { currentTab, totalLatex } = useSelector((state: RootState) => state.latex);
const currentTabInfo = useMemo(() => totalLatex.filter((latex) => latex.id === currentTab)[0], [
currentTab,
totalLatex,
]);
return { currentTabInfo, currentTab, totalLatex };
}
export default useCurrentTab;
useCurrentTab은 우리가 자주 사용하는 현재 탭의 정보를 가져오기 쉽게 만든 custom hook이다. 개발하다 보니 많은 곳에서 위의 로직을 사용하는 것을 확인할 수 있었다. 따라서 custom hook으로 분리해 반복되는 로직을 줄이고 해당 정보를 더욱 가져오기 쉽게 만들었다. currentTabInfo의 경우에는 currentTab, totalLatex가 바뀌지 않았을 경우 다시 실행될 필요가 없기 때문에 useMemo로 최적화를 해주었다.
import React from 'react';
import useToggle from './useToggle';
import { Button } from 'semantic-ui-react';
import styled from '@emotion/styled';
interface Props {
children?: React.ReactChild;
width?: 'mini' | 'big';
}
interface ModalType {
saveHandler?: (e: React.MouseEvent<HTMLElement>) => void | Promise<void>;
closeHandler?: (e: React.MouseEvent<HTMLElement>) => void;
needButton?: boolean;
}
interface ModalStyleProps {
width: 'mini' | 'big';
}
const Screen = styled.div`
width: 100%;
height: 100vh;
position: fixed;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
background: rgba(125, 125, 125, 0.7);
z-index: 5;
`;
const ModalContainer = styled.div<ModalStyleProps>`
z-index: 6;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
width: ${(props) => (props.width === 'mini' ? '15% ' : '40%')};
min-width: 250px;
max-width: 450px;
min-height: 150px;
max-height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 5px;
background-color: white;
& .content_wrapper {
width: 100%;
margin-bottom: 1rem;
& .screen_content {
font-size: 1em;
}
}
& button {
width: 84px;
}
& button:first-of-type {
margin-right: 10px !important;
}
& img {
width: 50%;
}
`;
const useModal = ({
saveHandler,
closeHandler,
needButton = false,
}: ModalType): [() => void, ({ children }: Props) => JSX.Element] => {
const [modalState, onToggleModal] = useToggle(false);
const onSaveHandler = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
saveHandler && saveHandler(e);
onToggModal();
};
const onCloseHandler = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
closeHandler && closeHandler(e);
onToggModal();
};
function Modal({ children, width = 'mini' }: Props): JSX.Element {
return (
<>
{modalState && (
<>
<ModalContainer width={width}>
<div className="content_wrapper">{children}</div>
{needButton && (
<div className="button_wrapper">
<Button onClick={onSaveHandler} positive>
Save
</Button>
<Button onClick={onCloseHandler}>Cancel</Button>
</div>
)}
</ModalContainer>
<Screen onClick={onCloseHandler} />
</>
)}
</>
);
}
return [onToggleModal, Modal];
};
export default useModal;
useModal은 modal을 쉽게 만들 수 있게 도와주는 custom hook이다. onToggleModal과 Modal 컴포넌트를 반환해준다. useModal의 인자로 저장할 때, 닫을 때 같이 실행될 콜백함수를 받아서 사용자가 마음대로 커스텀할 수 있게 하였다. 기본적으로 만들어져있는 버튼을 사용하고 싶을 경우 needButton을 true로 해주면 된다. 자신이 만든 button을 사용하고 싶으면 Modal 컴포넌트의 children으로 넣어주면 된다. 또한 자신이 Modal의 가운데 부분에 보여지고 싶은 화면을 children에 넣으면된다. useModal을 사용하여 반환되는 Modal 컴포넌트의 children으로 그려지길 원하는 컴포넌트를 넣으면 어디서든지 가운데에 Modal이 생기는 것을 확인할 수 있을 것이다.