Joi:无法更改可选数组的错误消息,如果有项目,它们不能是空字符串

发布于 2025-01-15 10:09:49 字数 6357 浏览 0 评论 0原文

我有一个需要描述的项目表格,如果他们想添加详细信息,则需要填写这些文本区域。但如果没有添加任何细节也没关系。因此,如果他们想添加详细信息,则必须填充该文本区域。但允许使用空数组。

我在覆盖缺少详细信息的默认错误时遇到问题,默认为“details[0]”不能是稀疏数组

表单在文本“为您的项目编写描述(必需)”下方显示一个空白文本区域,该文本区域下方是一个用于添加详细信息的按钮。

包含两个的表单文本区域,一个用于描述,另一个用于询问详细信息,该文本区域也是空白的。

文本区域现在在阅读中概述。描述文本区域下方有文本“需要描述”。 text 'details[0] 不能是稀疏数组项'。

架构:

const descriptionDetailSchema = Joi.object({ 
  description: Joi.string().required().messages({
    'string.base': 'Description is required',
    'string.empty': 'Description is required'
  }),
  details: Joi.array().items(
    Joi.string().messages({
      'string.empty': 'Detail is required'
    })
  )
});
const DescriptionAndDetailForm = forwardRef(
  ({ activityIndex, item, index, saveDescription, setFormValid }, ref) => {
    DescriptionAndDetailForm.displayName = 'DescriptionAndDetailForm';
    const {
      handleSubmit,
      control,
      formState: { error, errors, isValid, isValidating },
      getValues
    } = useForm({
      defaultValues: {
        description: item.description,
        details: item.details
      },
      mode: 'onBlur',
      reValidateMode: 'onBlur',
      resolver: joiResolver(descriptionDetailSchema)
    });

    useEffect(() => {
      console.log('isValid changed');
      console.log({ errors, isValid, isValidating });
      setFormValid(isValid);
    }, [isValid]);

    useEffect(() => {
      console.log('errors changed');
      console.log({ errors, isValid, isValidating });
    }, [errors]);

    useEffect(() => {
      console.log('isValidating changed');
      const { error, value } = descriptionDetailSchema.validate(getValues());
      console.log({ error, value, errors, isValid, isValidating });
    }, [isValidating, errors]);

    const initialState = item;

    function reducer(state, action) {
      switch (action.type) {
        case 'updateField':
          return {
            ...state,
            [action.field]: action.value
          };
        case 'addDetail': {
          const newDetail = newDescriptionDetail();
          return {
            ...state,
            details: [...state.details, newDetail]
          };
        }
        case 'removeDetail': {
          const detailsCopy = [...state.details];
          detailsCopy.splice(action.index, 1);
          return {
            ...state,
            details: detailsCopy
          };
        }
        case 'updateDetails': {
          const detailsCopy = [...state.details];
          detailsCopy[action.detailIndex].detail = action.value;

          return {
            ...state,
            details: detailsCopy
          };
        }
        default:
          throw new Error(
            'Unrecognized action type provided to DescriptionAndDetailForm reducer'
          );
      }
    }

    const [state, dispatch] = useReducer(reducer, initialState);

    const handleDescriptionChange = e => {
      dispatch({
        type: 'updateField',
        field: 'description',
        value: e.target.value
      });
    };

    const onSubmit = e => {
      e.preventDefault();
      saveDescription(activityIndex, index, state);
      handleSubmit(e);
    };

    const handleAddDetail = () => {
      dispatch({ type: 'addDetail' });
    };

    const handleDeleteDetail = (descriptionIndex, detailIndex) => {
      dispatch({ type: 'removeDetail', index: detailIndex });
    };

    const handleDetailChange = (e, i) => {
      dispatch({
        type: 'updateDetails',
        detailIndex: i,
        value: e.target.value
      });
    };

    return (
      <form
        index={index}
        key={`activity${activityIndex}-index${index}-form`}
        onSubmit={onSubmit}
      >
        <Controller
          key={`activity${activityIndex}-index${index}`}
          name="description"
          control={control}
          render={({ field: { onChange, ...props } }) => (
            <TextField
              {...props}
              label="Description"
              multiline
              rows="4"
              onChange={e => {
                handleDescriptionChange(e);
                onChange(e);
              }}
              errorMessage={errors?.description?.message}
              errorPlacement="bottom"
            />
          )}
        />
        {state.details.map(({ key, detail }, i) => (
          <Review
            key={key}
            onDeleteClick={ () => handleDeleteDetail(index, i) }
            onDeleteLabel="Remove"
            skipConfirmation
            ariaLabel={`${i + 1}. ${detail}`}
            objType="Detail"
          >
            <div>
              <Controller
                name={`details.${i}`}
                control={control}
                render={({ field: { onChange, ...props } }) => (
                  <TextField
                    {...props}
                    id={`${activityIndex}-detail${i}`}
                    name={`details.${i}`}
                    label="Detail"
                    value={detail}
                    multiline
                    rows="4"
                    onChange={e => {
                      handleDetailChange(e, i);
                      onChange(e);
                    }}
                    errorMessage={errors?.details && errors?.details[i]?.message}
                    errorPlacement="bottom"
                  />
                )}
              />
            </div>
          </Review>
        ))}
        <div>
          <Button
            key={`activity${activityIndex}-index${index}-add-metric`}
            onClick={handleAddDetail}
          >
            <Icon icon={faPlusCircle} />
            Add Detail
          </Button>
        </div>
        <input
          type="submit"
          ref={ref}
          hidden
        />
      </form>
    );
  }
);

I have a form for a project where the description is required, and if they want to add details, those textareas need to be filled out. But it's okay if there are no details added. So if they want to add a detail, that text area has to be filled. But an empty array is allowed.

I am having trouble overwriting the default error for the missing details, the default being "details[0]" must not be a sparse array.

A form that shows a blank textarea underneath the text 'Write a description for your project. (Required)' Beneath the textarea is a button to add a detail. The Save button is disabled.

A form with two textareas, one for the description and a new one asking for a detail that is also blank. The Save button is disabled.

The textareas are now outlined in read. The description textarea has the text 'Description required' underneath. The detail textarea has the text 'details[0] must not be a sparse array item'.

Schema:

const descriptionDetailSchema = Joi.object({ 
  description: Joi.string().required().messages({
    'string.base': 'Description is required',
    'string.empty': 'Description is required'
  }),
  details: Joi.array().items(
    Joi.string().messages({
      'string.empty': 'Detail is required'
    })
  )
});
const DescriptionAndDetailForm = forwardRef(
  ({ activityIndex, item, index, saveDescription, setFormValid }, ref) => {
    DescriptionAndDetailForm.displayName = 'DescriptionAndDetailForm';
    const {
      handleSubmit,
      control,
      formState: { error, errors, isValid, isValidating },
      getValues
    } = useForm({
      defaultValues: {
        description: item.description,
        details: item.details
      },
      mode: 'onBlur',
      reValidateMode: 'onBlur',
      resolver: joiResolver(descriptionDetailSchema)
    });

    useEffect(() => {
      console.log('isValid changed');
      console.log({ errors, isValid, isValidating });
      setFormValid(isValid);
    }, [isValid]);

    useEffect(() => {
      console.log('errors changed');
      console.log({ errors, isValid, isValidating });
    }, [errors]);

    useEffect(() => {
      console.log('isValidating changed');
      const { error, value } = descriptionDetailSchema.validate(getValues());
      console.log({ error, value, errors, isValid, isValidating });
    }, [isValidating, errors]);

    const initialState = item;

    function reducer(state, action) {
      switch (action.type) {
        case 'updateField':
          return {
            ...state,
            [action.field]: action.value
          };
        case 'addDetail': {
          const newDetail = newDescriptionDetail();
          return {
            ...state,
            details: [...state.details, newDetail]
          };
        }
        case 'removeDetail': {
          const detailsCopy = [...state.details];
          detailsCopy.splice(action.index, 1);
          return {
            ...state,
            details: detailsCopy
          };
        }
        case 'updateDetails': {
          const detailsCopy = [...state.details];
          detailsCopy[action.detailIndex].detail = action.value;

          return {
            ...state,
            details: detailsCopy
          };
        }
        default:
          throw new Error(
            'Unrecognized action type provided to DescriptionAndDetailForm reducer'
          );
      }
    }

    const [state, dispatch] = useReducer(reducer, initialState);

    const handleDescriptionChange = e => {
      dispatch({
        type: 'updateField',
        field: 'description',
        value: e.target.value
      });
    };

    const onSubmit = e => {
      e.preventDefault();
      saveDescription(activityIndex, index, state);
      handleSubmit(e);
    };

    const handleAddDetail = () => {
      dispatch({ type: 'addDetail' });
    };

    const handleDeleteDetail = (descriptionIndex, detailIndex) => {
      dispatch({ type: 'removeDetail', index: detailIndex });
    };

    const handleDetailChange = (e, i) => {
      dispatch({
        type: 'updateDetails',
        detailIndex: i,
        value: e.target.value
      });
    };

    return (
      <form
        index={index}
        key={`activity${activityIndex}-index${index}-form`}
        onSubmit={onSubmit}
      >
        <Controller
          key={`activity${activityIndex}-index${index}`}
          name="description"
          control={control}
          render={({ field: { onChange, ...props } }) => (
            <TextField
              {...props}
              label="Description"
              multiline
              rows="4"
              onChange={e => {
                handleDescriptionChange(e);
                onChange(e);
              }}
              errorMessage={errors?.description?.message}
              errorPlacement="bottom"
            />
          )}
        />
        {state.details.map(({ key, detail }, i) => (
          <Review
            key={key}
            onDeleteClick={ () => handleDeleteDetail(index, i) }
            onDeleteLabel="Remove"
            skipConfirmation
            ariaLabel={`${i + 1}. ${detail}`}
            objType="Detail"
          >
            <div>
              <Controller
                name={`details.${i}`}
                control={control}
                render={({ field: { onChange, ...props } }) => (
                  <TextField
                    {...props}
                    id={`${activityIndex}-detail${i}`}
                    name={`details.${i}`}
                    label="Detail"
                    value={detail}
                    multiline
                    rows="4"
                    onChange={e => {
                      handleDetailChange(e, i);
                      onChange(e);
                    }}
                    errorMessage={errors?.details && errors?.details[i]?.message}
                    errorPlacement="bottom"
                  />
                )}
              />
            </div>
          </Review>
        ))}
        <div>
          <Button
            key={`activity${activityIndex}-index${index}-add-metric`}
            onClick={handleAddDetail}
          >
            <Icon icon={faPlusCircle} />
            Add Detail
          </Button>
        </div>
        <input
          type="submit"
          ref={ref}
          hidden
        />
      </form>
    );
  }
);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

明媚如初 2025-01-22 10:09:49

如果您使用 .custom() 进行验证,您需要确保您的模式返回一个值

You need to make sure your schema is returning a value if you're using .custom() for validation

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文