Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] Input 검증 에러 메시지 타입 구조 변경 #376

Merged
merged 9 commits into from
Aug 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export interface LabelGroupInputStyleProps {}

export interface LabelGroupInputCustomProps {
labelText: string;
errorText?: string;
errorText: string | null;
}

export type LabelGroupInputOptionProps = LabelGroupInputStyleProps & LabelGroupInputCustomProps;
Expand Down
8 changes: 5 additions & 3 deletions HDesign/src/components/LabelInput/LabelInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ const LabelInput: React.FC<LabelInputProps> = forwardRef<HTMLInputElement, Label
<Text size="caption" css={labelTextStyle(theme, hasFocus, !!htmlProps.value)}>
{labelText}
</Text>
<Text size="caption" css={errorTextStyle(theme, isError ?? false)}>
{errorText}
</Text>
{errorText && (
<Text size="caption" css={errorTextStyle(theme, isError ?? false)}>
{errorText}
</Text>
)}
</Flex>
<Flex flexDirection="column" gap="0.5rem">
<Input ref={inputRef} isError={isError} placeholder={labelText} {...htmlProps} />
Expand Down
2 changes: 1 addition & 1 deletion HDesign/src/components/LabelInput/LabelInput.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export interface LabelInputStyleProps {}

export interface LabelInputCustomProps {
labelText: string;
errorText?: string;
errorText: string | null;
isError?: boolean;
autoFocus: boolean;
}
Expand Down
2 changes: 1 addition & 1 deletion client/src/ErrorProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {createContext, useState, useContext, useEffect, ReactNode} from 'react';

import SERVER_ERROR_MESSAGES from '@constants/errorMessage';
import {SERVER_ERROR_MESSAGES} from '@constants/errorMessage';

// 에러 컨텍스트 생성
interface ErrorContextType {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {FixedButton, LabelGroupInput} from 'haengdong-design';
import {useEffect} from 'react';

import validatePurchase from '@utils/validate/validatePurchase';

Expand All @@ -15,6 +16,7 @@ const AddBillActionListModalContent = ({setIsOpenBottomSheet}: AddBillActionList
const {
inputPairList,
inputRefList,
errorMessage,
errorIndexList,
handleInputChange,
getFilledInputPairList,
Expand All @@ -34,7 +36,7 @@ const AddBillActionListModalContent = ({setIsOpenBottomSheet}: AddBillActionList
return (
<div css={style.container}>
<div css={style.inputContainer}>
<LabelGroupInput labelText="지출내역 / 금액">
<LabelGroupInput labelText="지출내역 / 금액" errorText={errorMessage}>
{inputPairList.map(({index, title, price}) => (
<div key={index} css={style.input}>
<LabelGroupInput.Element
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Toast/ToastProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @jsxImportSource @emotion/react */
import {createContext, useContext, useEffect, useState} from 'react';

import SERVER_ERROR_MESSAGES from '@constants/errorMessage';
import {SERVER_ERROR_MESSAGES} from '@constants/errorMessage';

import {useError} from '../../ErrorProvider';

Expand Down
4 changes: 1 addition & 3 deletions client/src/constants/errorMessage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type ErrorMessage = Record<string, string>;

const SERVER_ERROR_MESSAGES: ErrorMessage = {
export const SERVER_ERROR_MESSAGES: ErrorMessage = {
EVENT_NOT_FOUND: '존재하지 않는 행사입니다.',
EVENT_NAME_LENGTH_INVALID: '행사 이름은 2자 이상 30자 이하만 입력 가능합니다.',
EVENT_NAME_CONSECUTIVE_SPACES: '행사 이름에는 공백 문자가 연속될 수 없습니다.',
Expand Down Expand Up @@ -45,5 +45,3 @@ export const ERROR_MESSAGE = {
};

export const UNKNOWN_ERROR = 'UNKNOWN_ERROR';

export default SERVER_ERROR_MESSAGES;
9 changes: 4 additions & 5 deletions client/src/hooks/useDynamicBillActionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type BillInputType = 'title' | 'price';
const useDynamicBillActionInput = (validateFunc: (inputPair: Bill) => ValidateResult) => {
const [inputPairList, setInputPairList] = useState<InputPair[]>([{title: '', price: '', index: 0}]);
const inputRefList = useRef<(HTMLInputElement | null)[]>([]);
const [errorMessage, setErrorMessage] = useState('');
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [errorIndexList, setErrorIndexList] = useState<number[]>([]);
const [canSubmit, setCanSubmit] = useState(false);

Expand All @@ -34,13 +34,14 @@ const useDynamicBillActionInput = (validateFunc: (inputPair: Bill) => ValidateRe
[field]: value,
});

setErrorMessage(validationResultMessage);

// TODO: (@weadie) 가독성이 안좋다는 리뷰. 함수로 분리
if (
isLastInputPairFilled({index, field, value}) &&
targetInputPair.title.trim().length !== 0 &&
targetInputPair.price.trim().length !== 0
) {
setErrorMessage('');
setInputPairList(prevInputPairList => {
const updatedInputPairList = [...prevInputPairList];

Expand All @@ -52,19 +53,17 @@ const useDynamicBillActionInput = (validateFunc: (inputPair: Bill) => ValidateRe
});
} else if (isValidInput) {
// 입력된 값이 유효하면 데이터(inputLis)를 변경합니다.
setErrorMessage('');
if (errorIndexList.includes(index)) {
setErrorIndexList(prev => prev.filter(i => i !== index));
}

changeInputListValue(index, value, field);
} else if (value.length === 0) {
setErrorMessage('');
setErrorMessage(null);
changeErrorIndex(index);

changeInputListValue(index, value, field);
} else {
setErrorMessage(validationResultMessage ?? '');
changeErrorIndex(targetInputPair.index);
}

Expand Down
11 changes: 4 additions & 7 deletions client/src/hooks/useDynamicInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type ReturnUseDynamicInput = {
inputRefList: React.MutableRefObject<(HTMLInputElement | null)[]>;
handleInputChange: (index: number, event: React.ChangeEvent<HTMLInputElement>) => void;
deleteEmptyInputElementOnBlur: () => void;
errorMessage: string;
errorMessage: string | null;
getFilledInputList: (list?: InputValue[]) => InputValue[];
focusNextInputOnEnter: (e: React.KeyboardEvent<HTMLInputElement>, index: number) => void;
canSubmit: boolean;
Expand All @@ -23,7 +23,7 @@ export type ReturnUseDynamicInput = {
const useDynamicInput = (validateFunc: (name: string) => ValidateResult): ReturnUseDynamicInput => {
const [inputList, setInputList] = useState<InputValue[]>([{index: 0, value: ''}]);
const inputRefList = useRef<(HTMLInputElement | null)[]>([]);
const [errorMessage, setErrorMessage] = useState('');
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [errorIndexList, setErrorIndexList] = useState<number[]>([]);
const [canSubmit, setCanSubmit] = useState(false);

Expand Down Expand Up @@ -62,11 +62,10 @@ const useDynamicInput = (validateFunc: (name: string) => ValidateResult): Return
// onChange와 setValue 둘 다 지원하기 위해서 validate를 분리
const validateAndSetTargetInput = (index: number, value: string) => {
const {isValid: isValidInput, errorMessage: validationResultMessage} = validateFunc(value);
setErrorMessage(validationResultMessage);

if (isValidInput) {
// 입력된 값이 유효하면 데이터(inputList)를 변경합니다.
setErrorMessage('');

if (errorIndexList.includes(index)) {
setErrorIndexList(prev => prev.filter(i => i !== index));
}
Expand All @@ -75,16 +74,14 @@ const useDynamicInput = (validateFunc: (name: string) => ValidateResult): Return
} else if (value.length === 0) {
// value의 값이 0이라면 errorMessage는 띄워지지 않지만 값은 변경됩니다. 또한 invalid한 값이기에 errorIndex에 추가합니다.

setErrorMessage('');
setErrorMessage(null);
changeErrorIndex(index);

changeInputListValue(index, value);
} else {
// 유효성 검사에 실패한 입력입니다. 에러 메세지를 세팅합니다.

const targetInput = findInputByIndex(index);

setErrorMessage(validationResultMessage ?? '');
changeErrorIndex(targetInput.index);
}
};
Expand Down
8 changes: 4 additions & 4 deletions client/src/hooks/usePutAndDeleteBillAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {useFetch} from '@apis/useFetch';

import getEventIdByUrl from '@utils/getEventIdByUrl';

import ERROR_MESSAGE from '@constants/errorMessage';
import {ERROR_MESSAGE} from '@constants/errorMessage';

const usePutAndDeleteBillAction = (
initialValue: InputPair,
Expand All @@ -26,7 +26,7 @@ const usePutAndDeleteBillAction = (
const [inputPair, setInputPair] = useState<InputPair>(initialValue);
const [canSubmit, setCanSubmit] = useState(false);
const [errorInfo, setErrorInfo] = useState<Record<string, boolean>>({title: false, price: false});
const [errorMessage, setErrorMessage] = useState<string | undefined>();
const [errorMessage, setErrorMessage] = useState<string | null>(null);

const handleInputChange = (field: BillInputType, event: React.ChangeEvent<HTMLInputElement>) => {
const {value} = event.target;
Expand All @@ -42,9 +42,10 @@ const usePutAndDeleteBillAction = (

const {isValid, errorMessage, errorInfo} = validateFunc(getFieldValue());

setErrorMessage(errorMessage);

if (isValid) {
// valid일 경우 에러메시지 nope, setValue, submit은 value가 비지 않았을 때 true를 설정
setErrorMessage(undefined);
setInputPair(prevInputPair => {
return {
...prevInputPair,
Expand All @@ -55,7 +56,6 @@ const usePutAndDeleteBillAction = (
} else {
// valid하지 않으면 event.target.value 덮어쓰기
event.target.value = inputPair[field];
setErrorMessage(errorMessage);
setCanSubmit(false);
}

Expand Down
48 changes: 48 additions & 0 deletions client/src/hooks/useSetPassword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {useState} from 'react';

import validateEventPassword from '@utils/validate/validateEventPassword';

import {useFetch} from '@apis/useFetch';

import RULE from '@constants/rule';

import useEvent from './useEvent';

const useSetPassword = (eventName: string) => {
const {fetch} = useFetch();
const [password, setPassword] = useState('');
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [canSubmit, setCanSubmit] = useState(false);
const {createNewEvent} = useEvent();

const submitPassword = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

const {eventId} = await fetch({queryFunction: () => createNewEvent({eventName, password: parseInt(password)})});
return eventId;
};

const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
const {isValid, errorMessage} = validateEventPassword(newValue);

setCanSubmit(newValue.length === RULE.maxEventPasswordLength);
setErrorMessage(errorMessage);

if (isValid) {
setPassword(newValue);
} else {
event.target.value = password;
}
};

return {
password,
errorMessage,
canSubmit,
submitPassword,
handlePasswordChange,
};
};

export default useSetPassword;
2 changes: 1 addition & 1 deletion client/src/utils/validate/type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface ValidateResult {
isValid: boolean;
errorMessage?: string;
errorMessage: string | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined보다 확실히 nul이 더 좋은 것 같아요!

errorInfo?: Record<string, boolean>;
}
4 changes: 2 additions & 2 deletions client/src/utils/validate/validateEventName.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ERROR_MESSAGE from '@constants/errorMessage';
import {ERROR_MESSAGE} from '@constants/errorMessage';
import RULE from '@constants/rule';

import {ValidateResult} from './type';
Expand All @@ -7,7 +7,7 @@ const validateEventName = (name: string): ValidateResult => {
if (name.length > RULE.maxEventNameLength) {
return {isValid: false, errorMessage: ERROR_MESSAGE.eventName};
}
return {isValid: true};
return {isValid: true, errorMessage: null};
};

export default validateEventName;
4 changes: 2 additions & 2 deletions client/src/utils/validate/validateEventPassword.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ERROR_MESSAGE from '@constants/errorMessage';
import {ERROR_MESSAGE} from '@constants/errorMessage';
import REGEXP from '@constants/regExp';

import {ValidateResult} from './type';
Expand All @@ -7,7 +7,7 @@ const validateEventPassword = (password: string): ValidateResult => {
if (!REGEXP.eventPassword.test(password)) {
return {isValid: false, errorMessage: ERROR_MESSAGE.eventPasswordType};
}
return {isValid: true};
return {isValid: true, errorMessage: null};
};

export default validateEventPassword;
2 changes: 1 addition & 1 deletion client/src/utils/validate/validateMemberName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const validateMemberName = (name: string): ValidateResult => {
};

if (validateOnlyString() && validateLength()) {
return {isValid: true};
return {isValid: true, errorMessage: null};
}

return {isValid: false, errorMessage: ERROR_MESSAGE.memberName};
Expand Down
6 changes: 3 additions & 3 deletions client/src/utils/validate/validatePurchase.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type {Bill} from 'types/serviceType';

import ERROR_MESSAGE from '@constants/errorMessage';
import {ERROR_MESSAGE} from '@constants/errorMessage';
import RULE from '@constants/rule';
import REGEXP from '@constants/regExp';

import {ValidateResult} from './type';

const validatePurchase = (inputPair: Bill): ValidateResult => {
const {title, price} = inputPair;
let errorMessage;
let errorMessage: string | null = null;

const errorInfo = {
price: false,
Expand Down Expand Up @@ -38,7 +38,7 @@ const validatePurchase = (inputPair: Bill): ValidateResult => {
};

if (validatePrice() && validateTitle()) {
return {isValid: true, errorMessage: ''};
return {isValid: true, errorMessage: null};
}

return {isValid: false, errorMessage, errorInfo};
Expand Down
Loading