Skip to content

Commit

Permalink
feat: Input 검증 에러 메시지 타입 구조 변경 (#376)
Browse files Browse the repository at this point in the history
* refactor: errorMessage 타입을 string | undefined에서 string | null로 변경

* refactor: 에러가 아닐 때 errorMessage를 null로 리턴

* fix: 에러 import 되지 않던 현상 해결

* refactor: 에러 메시지 validate 함수 리턴하는 대로 그대로 set

* feat: 에러메시지 보여지도록 추가

* refactor: 에러메시지 타입 string | null로 변경
  • Loading branch information
jinhokim98 authored Aug 16, 2024
1 parent ee322f6 commit 7d54702
Show file tree
Hide file tree
Showing 16 changed files with 36 additions and 39 deletions.
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
5 changes: 2 additions & 3 deletions client/src/hooks/useSetPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import useEvent from './useEvent';
const useSetPassword = (eventName: string) => {
const {fetch} = useFetch();
const [password, setPassword] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [canSubmit, setCanSubmit] = useState(false);
const {createNewEvent} = useEvent();

Expand All @@ -27,13 +27,12 @@ const useSetPassword = (eventName: string) => {
const {isValid, errorMessage} = validateEventPassword(newValue);

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

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

Expand Down
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;
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

0 comments on commit 7d54702

Please sign in to comment.