프론트엔드 개발자라면 한번쯤 Input과 validation을 놓고 싸워본적이 있을거라 생각해요 (🤢 벌써부터 토나오는군요?)
회원 가입, 수정, 비밀번호 변경, 리뷰 입력, 댓글입력 등 여러 상황에서 매번 다른 조건에 따라 validation을 체크하고 form을 입력하고.. 😫
저또한 매번 구현을 하면서 이러한 과정을 모두 겪었을텐데 최선은 없을까? 이게 최선일까? 항상 고민이 됬었고, 이럴바에 차라리 계속 쓸수있는 input 관리 방법을 만들어보자!! 하며 나름대로 최선을 찾아 useInputManager라는 이름의 Hook을 구현하게 되었고 소개해드리려고 합니다.
useInputManager
는 호출하면서 인자에 input_name_set
을 통해 사용할 input을 생성해 줍니다.
useInputManager({
input_name_set: ["user_id", "password", "re_password"]
})
이렇게 생성한 input은 useInputManager
를 호출하면서 함께 return 받은 Inputs
에서 꺼내서 쓸수 있는데요, 아래와 같은 형태가 될것 같내요?
const {Inputs}
= useInputManager({
input_name_set: ["user_id", "password", "re_password"]
})
...
<Inputs.user_id />
<Inputs.password />
<Inputs.re_password />
...
이렇게 훅을 통해 만들어진 input은 아래와같이 error massage를 하단에 표시할수 있는 기능을 갖고 있어요,
에러메시지는 훅을 호출하면서 return 받은 err_msg_invalid_handlers
를 통해 사용할 수 있습니다.
const {
err_msg_invalid_handlers,
Inputs
} = useInputManager({
input_name_set: ["user_id", "password", "re_password"]
})
...
<Inputs.user_id
onTextChange={
(text)=>{
err_msg_invalid_handlers.email.set(
"이메일 형태로 입력해 주세요 😭",
//이메일이 아닐경우 위의 메시지 표시
!Utils.isEmail(text))
}}
/>
...
err_msg_invalid_handlers
은 input_name_set
의 이름을 사용해 에러메시지를 핸들링 할 수 있기 때문에 비밀번호 재입력 체크와 같이 번거로운 크로스체크같은 상황이 아주 쉽게 해결이 될 수 있습니다~
...
<Inputs.password
onTextChange={
...
(text)=>{
//password 입력 input에서 패스워드 재입력 input 에러메시지 핸들링
err_msg_invalid_handlers.re_password.set(
"비밀번호가 서로 맞지 않아요 😭",
text !== entered.re_password
}}
/>
...
그리고 사용자가 회원가입창에 들어와서 아무것도 입력하지 않고 가입버튼을 누르는 상황과 같은 경우에는 setInputsErrorMessage
를 훅에서 return받아 전체 input을 각각 관리할 수도 있습니다.
const {
setInputsErrorMessagem,
err_msg_invalid_handlers,
Inputs
} = useInputManager({
input_name_set: ["user_id", "password", "re_password"]
})
...
setInputsErrorMessage({
user_id: "id가 입력되지 않았어요!",
password: "password가 입력되지 않았어요!",
re_password: "password가 입력되지 않았어요!",
})
마지막으로 validation 체크를 할 수 있는데, 예를들어 회원가입 입력사항을 전부 입력한 후 가입 버튼을 누를때일거 같내요? 이럴땐 checkInputsInvalidate
를 통해 validation을 체크할 수 있습니다.
const {
checkInputsInvalidate,
setInputsErrorMessagem,
err_msg_invalid_handlers,
Inputs
} = useInputManager({
input_name_set: ["user_id", "password", "re_password"]
})
...
//invalidation이 아닐경우 회원가입
!checkInputsInvalidate() ? "성공!" : "실패"
디버그 모드에선 콘솔을 통해 어떤 input이 통과되지 않았는지 확인할수도 있습니다.
어때요 참쉽죠? 😛
💡 여기에서 소개해드리지 못한 내용들도 있지만 다른 컴포넌트와 연계되어있는 부분도 있고, 코드로 이해가 가능한 부분도 있어 아래 코드를 참고 하시면 좋을것 같아요!
뭔가 제가 너무 불편해서 만들어 보았는데, 쓰다보니 편한거 같아 소개해보았는데요? 사실 그대로 사용하기에는 좀 무리가 있지 싶은 코드라 그냥 이렇게도 하는구나~ 하면서 봐주시고 또 각자의 노하우를 공유해주시거나 발전시킬수 있는 부분을 코멘트 해주시면 좋을것 같아요!
아래에 코드를 끝으로 useInputManager
소개는 마치도록 하겠습니다.
긴글 읽어주셔서 감사합니다. 🙇♂️
//배열로 넣어준 key 값을 통해 함께 관리되는 invalidate input 을 다루기위한 hook
export const useInputManager = ({
//생성하기 위한 input name 배열
input_name_set = [],
//invalidate 초기 값
initial_inputs_invalidate = true,
//handler set 이 실행될 때 callback
handlerSetCallback,
}) => {
//manager 로 생성된 input 의 invalidate 값
const inputs_invalidate = useMemo(() => {
const _invalidate = {};
input_name_set.forEach((input) => {
_invalidate[input] = initial_inputs_invalidate;
});
return _invalidate;
}, []);
//manager 로 생성된 input 의 error message 를 핸들링하기 위한 객체, 각 input 의 키값을 통해 set 을 사용할 수있음
const err_msg_invalid_handlers = (() => {
const handlers = {};
input_name_set.forEach((key) => {
handlers[key] = {
//input 과 handler 를 매칭 시켜주기위한 ref
ref: useRef(""),
//error message 를 표시하기위한 함수, invalid condition 을 기준으로 메시지를 표시 함
set: (message, invalid_condition = initial_inputs_invalidate) => {
inputs_invalidate[key] = invalid_condition;
handlerSetCallback && handlerSetCallback();
invalid_condition ? handlers[key].ref.current(message) : handlers[key].ref.current("");
},
};
});
return handlers;
})();
//한번에 input 들의 error message 를 표시하기 위한 함수
const setInputsErrorMessage = (error_messages = {}) => {
input_name_set.forEach((key) => {
if (!error_messages[key]) return;
//invalidate 한 input error 를 표시
inputs_invalidate[key] && err_msg_invalid_handlers[key].set(error_messages[key], true);
});
};
//input 의 invalidate 값을 체크, 전부 통과 되었을 때만 false 를 return
const checkInputsInvalidate = () => {
console.log("check inputs invalidate", inputs_invalidate);
return Object.values(inputs_invalidate).some((is_invalidate) => is_invalidate);
};
//manager 로 생성한 input 을 사용하기 위한 컴포넌트 <Input.key /> 로 사용함
const Inputs = () => {
const _Inputs = {};
input_name_set.forEach((input) => {
_Inputs[input] = forwardRef((props, ref) => {
return <_Input ref={ref} {...props} _name={input} />;
});
});
return _Inputs;
};
//manager 내부에서 사용하기위한 컴포넌트
const _Input = forwardRef((props, ref) => {
const {
on_text_changed,
_name,
on_clear,
Component, // PasswordInput, DisplayIconInput...
} = props;
const new_props = {
...props,
input_error_message_handler: err_msg_invalid_handlers[_name].ref,
on_text_changed: (text) => {
text.current = text;
on_text_changed && on_text_changed(text);
},
on_clear: () => {
err_msg_invalid_handlers[_name].set("");
on_clear && on_clear();
},
};
if (Component) {
return <Component ref={ref} {...new_props} />;
} else {
return <ErrorMessageInput ref={ref} {...new_props} />;
}
});
return {
inputs_invalidate,
err_msg_invalid_handlers,
setInputsErrorMessage,
checkInputsInvalidate,
Inputs: useMemo(Inputs, []),
};
};