@sajkeekloch

Как правильно создать компонент, содержащий несколько разных компонентов, в зависимости от значения переменной?

Ошибка Rendered more hooks than during the previous render

Всем привет!
столкнулся с такой ошибкой при попытке создать генератор компонентов, который, в зависимости от переданных параметров, отрисовывает набор полей, получаю ошибку Rendered more hooks than during the previous render. она возникает, при изменении параметров формы
прошу накинуть идей, как это можно реализовать правильно, либо указать на мои ошибки)

пример - отрисовали одно поле формы с типом string - все ок, затем изменили параметры, теперь нужно отрисовать несколько полей (date, string) - получаем ошибки:

Warning: React has detected a change in the order of Hooks called by FormGenerator. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://fb.me/rules-of-hooks

Previous render Next render
------------------------------------------------------
1. useState useState
2. useCallback useCallback
3. useEffect useEffect
4. undefined useState
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
и
Rendered more hooks than during the previous render.


подробнее

подробнее:
есть задача, получаем набор полей формы - необходимо отрисовать эти поля, поля валидируют сами себя с помощью хуков, которые добавляют ошибки валидации в стор

return (
    <Form initialValues={initialValues} validate={validate} onSubmit={handleSubmit} ref={formRef}>
      {({ touched, values, setFieldValue, setFieldTouched }) => {
        return (
            <>
		…
              {условие && (
                <FormGenerator
                  forms={templates[currTemplate.value]?.forms}
                  touched={touched}
                  setFieldValue={setFieldValue}
                  formValues={values}
                  setFieldTouched={setFieldTouched}
                />
              )}
             …
        )
      }}
    </Form>
  )



FormGenerator, в свою очередь получает поля формы и отрисовывает массив компонентов, в зависимости от типа поля формы
const composeForm = forms => {
    const fields = getFileds(forms)
    const formFields = fields?.reduce(
      (acc, field) => {
        const fieldId = getFieldId(field.id)
        const value = data?.formValues?.customData[fieldId]
        const touched = data?.touched?.customData && data?.touched?.customData[fieldId]
        const fieldData = {
          ...field,
          ...data,
          value,
          touched,
          fieldId,
        }
        if (inputHandlerDetector(fieldData)) {
          return [...acc, inputHandlerDetector(fieldData)(fieldData)]
        } else return acc
      }, 
      [])
    return formFields
  }
  return composeForm(props.forms)


inputHandlerDetector возвращает компонент, который отрисовывает элемент формы

export const stringFieldHandler = data => {
  const [error, setError] = useState('')
  const { fieldId, id, matches, max, min, message, name, value, touched, setFieldValue, required, mask, disabled, type, typeName } = data

  const validate = useCallback(() => {
    if (!value?.length && required) {
      setError(requiredFieldError)
      addPersonnelActionErrorEvent({
        [fieldId]: requiredFieldError
      })
    } else if (matches && !russianEnglishDotsNumbersCommasDashsParentheses.test(value)) {
      setError(message ? message : 'Значение не соответствует формату')
      addPersonnelActionErrorEvent({
        [fieldId]: message ? message : 'Значение не соответствует формату'
      })
    } else if (max && value.length > max) {
      setError(`Значение должно быть не более ${max} символов`)
      addPersonnelActionErrorEvent({
        [fieldId]: `Значение должно быть не более ${max} символов`
      })
    } else if (min && value.length < min) {
      setError(`Значение должно быть не менее ${min} символов`)
      addPersonnelActionErrorEvent({
        [fieldId]: `Значение должно быть не менее ${min} символов`
      })
    } else if (id === 'custom/position' && !russianEnglishDotsNumbersSymbols.test(value)) {
      setError(russianLettersError)
      addPersonnelActionErrorEvent({
        [fieldId]: russianLettersError
      })
    } else {
      setError('')
      addPersonnelActionErrorEvent({
        [fieldId]: ''
      })
    }
  }, [required, fieldId, value, id, matches, max, min, message]) 

  useEffect(() => {
    validate()
  }, [value])

  return (
    <FieldFormik
      key={fieldId}
      id={`customData.${fieldId}`}
      label={getFieldLabel(name, required)}
      error={touched && error}
      value={value}
      mask={mask}
      onChange={event => {
        const { target } = event
        let { value } = target
        if (type === 'float') {
          value = processNumber(value, precision)
        } else if (type === 'int') {
          value = processInteger(value)
        }
        setFieldValue(`customData.${fieldId}`, value)
      }}
      autoComplete="off"
      placeholder={getPlaceholder(type, typeName)}
      disabled={disabled}
    />
  )
}


все работает, если валидацию вынести в родительский компонент, который содержит FormGenerator, но хочется валидировать поле в компоненте этого поля
  • Вопрос задан
  • 224 просмотра
Пригласить эксперта
Ответы на вопрос 1
artygrand
@artygrand
Прогер, кодер, писатель кода
По коду не понятно, где у тебя хуки, но общая система должна быть примерно такая:

Внутри Form
. . useState с дефолтными данными данными из пропса
. . useState со списком полей и их настройками, с дефолтными данными данными из пропса

. . fields.filter(f => f.active).map(f => < Field type data setField settings />)

Внутри Field
. . useState с текущим данным
. . useState с ошибкой
. . функция onChange с валидацией, которая либо сохраняет правильное значение и кастует setField, либо вызывает ошибку

. . конкретный инпут в зависимости от настроек

таким образом ты и поля можешь менять в зависимости от значений других полей, и валидируется каждый внутри себя
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы