하나로 뭉친 InputContainer

import React from 'react';
import { uid } from 'react-uid';
import { findNotCompletedInput } from '../../utils/util';
import Input from '../Input';
import PropTypes from 'prop-types';
import { useMouseHover } from '../../hooks/useMouseHover';
const INPUT_TYPE = {
  'cardNumber-first': 'text',
  'cardNumber-second': 'text',
  'cardNumber-third': 'password',
  'cardNumber-forth': 'password',
  'expirationDate-month': 'text',
  'expirationDate-year': 'text',
  ownerName: 'text',
  securityCode: 'password',
  'password-first': 'text',
  'password-second': 'text',
};
const INPUT_PLACEHOLDER = {
  ownerName: '카드에 표시된 이름과 동일하게 입력하세요.',
};
function InputContainer({
  state,
  stateName,
  dispatch,
  inputElementsRef,
  validate,
  setIsShowVirtualKeyboard,
  maxLength,
  multiInput,
  required,
  actionType,

  labelTitle,
  helpText,
  inputSize,
}) {
  const [isMouseOver, onMouseOver, onMouseLeave] = useMouseHover();

  const onChange = (e, stateKey) => {
    const {
      target: { value, maxLength },
    } = e;

    if (validate(value, maxLength)) {
      dispatch({ type: actionType, payload: { value, stateKey, stateName, multiInput } });
    }

    if (value.length === maxLength) {
      const key = stateKey ? `${stateName}-${stateKey}` : stateName;
      const { current: inputElementsMap } = inputElementsRef;

      const {
        nextInput: { element },
      } = findNotCompletedInput(inputElementsMap, key);

      inputElementsMap[key].isComplete = true;

      element?.focus();
    }
  };
  return (
    <div className="input-container">
      <span className="input-title">{labelTitle}</span>
      <div className={`input-box ${inputSize}`}>
        {multiInput ? (
          Object.keys(state).map(stateKey => (
            <Input
              key={uid(stateKey)}
              className="input-basic"
              type={INPUT_TYPE[`${stateName}-${stateKey}`]}
              value={state[stateKey]}
              onChange={e => onChange(e, stateKey)}
              required={required}
              maxLength={maxLength}
              elementKey={`${stateName}-${stateKey}`}
              inputElementsRef={inputElementsRef}
              setIsShowVirtualKeyboard={setIsShowVirtualKeyboard}
            />
          ))
        ) : (
          <Input
            className="input-basic"
            type={INPUT_TYPE[stateName]}
            value={state}
            onChange={onChange}
            required={required}
            maxLength={maxLength}
            placeholder={INPUT_PLACEHOLDER[stateName]}
            elementKey={stateName}
            inputElementsRef={inputElementsRef}
            setIsShowVirtualKeyboard={setIsShowVirtualKeyboard}
          />
        )}

        {helpText && (
          <div className="help-content" onMouseOver={onMouseOver} onMouseLeave={onMouseLeave}>
            ?
          </div>
        )}

        {isMouseOver && <div className="help-content-text">{helpText}</div>}
      </div>
    </div>
  );
}

InputContainer.propTypes = {
  state: PropTypes.any,
  stateName: PropTypes.any,
  dispatch: PropTypes.any,
  labelTitle: PropTypes.any,
  inputElementsRef: PropTypes.any,
  validate: PropTypes.any,
  setIsShowVirtualKeyboard: PropTypes.any,
  maxLength: PropTypes.number,
  inputSize: PropTypes.string,
  helpText: PropTypes.any,
  multiInput: PropTypes.any,
  required: PropTypes.bool,
  actionType: PropTypes.string,
};

export default InputContainer;

인자 정보를 모아둔.. 정적 객체

(변경되는 값으로 바뀌는 경우 여기서 삭제하고 상태화해야함.. 불편)

import { isAlphabetOrSpace } from '../../utils/validations';
import { isNumberInRange } from '../InputForm/validation';

export const TYPE_INPUT_INFORMATION = {
  CARD_NUMBER: {
    multiInput: true,
    stateName: 'cardNumber',
    labelTitle: '카드번호',
    validate: isNumberInRange,
    maxLength: 4,
    required: true,
    actionType: 'CHANGE_NUMBER_INPUT',
  },
  EXPIRATION_DATE: {
    multiInput: true,
    stateName: 'expirationDate',
    labelTitle: '만료일 (01~12의 월 / 년도)',
    validate: isNumberInRange,
    maxLength: 2,
    required: true,
    actionType: 'CHANGE_NUMBER_INPUT',
    inputSize: 'w-50',
  },
  OWNER_NAME: {
    multiInput: false,
    stateName: 'ownerName',
    labelTitle: '카드 소유자 이름(선택) (영문만 입력가능)',
    validate: isAlphabetOrSpace,
    maxLength: 30,
    required: false,
    actionType: 'CHANGE_UPPER_TEXT_INPUT',
  },
  SECURITY_CODE: {
    multiInput: false,
    stateName: 'securityCode',
    labelTitle: '보안코드(CVC/CVV)',
    validate: isNumberInRange,
    maxLength: 3,
    required: true,
    actionType: 'CHANGE_NUMBER_INPUT',
    inputSize: 'w-25',
    helpText: '카드 뒷면 서명란 또는 신용카드 번호 오른쪽 상단에 기재된 3자리 숫자',
  },
  PASSWORD: {
    multiInput: true,
    stateName: 'password',
    labelTitle: '카드 비밀번호',
    validate: isNumberInRange,
    maxLength: 1,
    required: true,
    actionType: 'CHANGE_NUMBER_INPUT',
    inputSize: 'w-50',
  },
};

reducer에 상태 변경 로직 추가

case 'CHANGE_NUMBER_INPUT': {
      const { stateKey, value, stateName, multiInput } = payload;
      return {
        ...state,
        [stateName]: multiInput ? { ...state[stateName], [`${stateKey}`]: value } : value,
      };
    }
    case 'CHANGE_UPPER_TEXT_INPUT': {
      const { stateKey, value, stateName, multiInput } = payload;
      return {
        ...state,
        [stateName]: multiInput
          ? { ...state[stateName], [`${stateKey}`]: value.toUpperCase() }
          : value.toUpperCase(),
      };
    }