Доступ к состоянию после того, как оно правильно установлено внутри пользовательского хука с помощью React Hooks

#javascript #reactjs #validation #react-hooks

#javascript #reactjs #проверка #реагирующие хуки

Вопрос:

Возможно, это неправильный вопрос. Но у меня возникла проблема с хуком проверки пользовательской формы, который не считывает входные значения, когда они обновляются с помощью http-запроса.

Вот CodeSandbox, если вы хотите его опробовать.

Это пользовательский хук useValidatedForm

 const [
    profileFormData,
    profileFormValidation,
    validateProfileForm,
    getProfileFormData
  ] = useValidatedForm(
    profileFormInitialState,
    profileFormValidationDefinitions
  );
  

У меня есть состояние для profileFormInitialState

 const [profileFormInitialState, setProfileFormInitialState] = useState({
    firstName: ""
  });
  

Я обновляю это состояние в http-запросе (фиктивный http-запрос на данный момент)

 useEffect(() => {
    const fn = "first name";
    setProfileFormInitialState({
      firstName: fn
    });
    setContent({ title: "My Personal Details" });
    setLoading({ status: false });
  }, []);
  

Это моя форма, которая отображается в DOM. Входное значение устанавливается через profileFormInitialState состояние.

 const form = (
    <>
      <FormControl
        key={"profileForm"}
        submit={profileFormSubmit}
        form={profileFormData}
        validation={profileFormValidation}
      >
        <InputControl
          autoComplete="off"
          type="text"
          name="firstName"
          placeholder={profileFormInitialState.firstName}
          value={profileFormInitialState.firstName}
          onInput={e =>
            setProfileFormInitialState({ firstName: e.target.value })
          }
          onChange={e => e.preventDefault()}
          label="First Name*"
          columns="2"
          position="left"
        >
          <ErrorMsg map="required" msg="First Name is required" />
        </InputControl>
        <InputButton
          modifier={"Button--profile"}
          disabled={!profileFormValidation.valid}
          buttonText="Update Profile"
          type="submit"
        />
      </FormControl>
    </>
  );
  

Ниже приведен мой useValidatedForm пользовательский хук

 import { useState } from "react";
import ValidaJS from "valida-js";

function stateFactory(fields) {
  return Object.keys(fields).reduce((acc, key) => {
    acc[key] = {
      value: fields[key],
      meta: {
        touched: false,
        dirty: false
      }
    };
    return acc;
  }, {});
}

function emptyErrorFactory(fields) {
  return Object.keys(fields).reduce((acc, key) => {
    acc[key] = [];
    return acc;
  }, {});
}

function rulesByNameFactory(descriptors, validators) {
  const descriptorBy = descriptors.reduce((acc, descriptor) => {
    acc[descriptor.type] = acc[descriptor.type];
    acc[descriptor.name] = acc[descriptor.name]
      ? acc[descriptor.name].concat([descriptor])
      : [descriptor];
    return acc;
  }, {});
  return Object.keys(descriptorBy).reduce(
    (acc, key) => {
      acc[key] = ValidaJS.rulesCreator(validators, descriptorBy[key]);
      return acc;
    },
    { default: ValidaJS.rulesCreator(validators, descriptors) }
  );
}

function getDataFromState(state) {
  return Object.keys(state).reduce((acc, key) => {
    acc[key] = state[key].value;

    return acc;
  }, {});
}

function extendsValidations(key, validation, newErrors = []) {
  const newValidation = {
    errors: {
      ...validation.errors,
      [key]: newErrors
    }
  };
  newValidation["valid"] = Object.keys(newValidation.errors).every(errorKey => {
    return newValidation.errors[errorKey].length === 0;
  });
  return newValidation;
}

function onChangeHandlerByKey(
  state,
  key,
  setState,
  setValidation,
  validation,
  rulesBy
) {
  return event => {
    const newState = {
      ...state,
      [key]: {
        ...state[key],
        value:
          event.currentTarget.type == "checkbox"
            ? event.currentTarget.checked
            : event.currentTarget.value,
        meta: {
          ...state[key].meta,
          dirty: true
        }
      }
    };
    const newErrors = ValidaJS.validate(
      rulesBy[key],
      getDataFromState(newState)
    ).errors[key];
    setState(newState);
    setValidation(extendsValidations(key, validation, newErrors));
  };
}

function onClickHandlerByKey(state, key, setState) {
  return _ => {
    setState({
      ...state,
      [key]: {
        ...state[key],
        meta: {
          ...state[key].meta,
          touched: true
        }
      }
    });
  };
}

function formDataFactory(state, setState, setValidation, validation, rulesBy) {
  return Object.keys(state).reduce((acc, key) => {
    acc[key] = {
      meta: state[key].meta,
      input: {
        value: state[key].value,
        onClick: onClickHandlerByKey(
          state,
          key,
          setState,
          setValidation,
          validation,
          rulesBy
        ),
        onChange: onChangeHandlerByKey(
          state,
          key,
          setState,
          setValidation,
          validation,
          rulesBy
        )
      }
    };
    return acc;
  }, {});
}

const useValidatedForm = (
  fields = {},
  descriptors = [],
  validators = ValidaJS.validators
) => {
  const initialErrorsObj = emptyErrorFactory(fields);
  const initialState = stateFactory(fields);
  console.log("initial state = "   initialState.firstName.value);
  const [state, setState] = useState(initialState);
  console.log("state = "   state.firstName.value);
  const [validation, setValidation] = useState({
    valid: true,
    errors: initialErrorsObj
  });
  const rulesBy = rulesByNameFactory(descriptors, validators);
  const form = formDataFactory(
    state,
    setState,
    setValidation,
    validation,
    rulesBy
  );

  const getData = () => getDataFromState(state);
  const setData = data => setState(stateFactory(data));
  const validate = () => {
    const newValidations = ValidaJS.validate(
      rulesBy.default,
      getDataFromState(state)
    );
    setValidation({
      ...newValidations,
      errors: { ...initialErrorsObj, ...newValidations.errors }
    });
    return newValidations.valid;
  };

  return [form, validation, validate, getData, setData];
};

export default useValidatedForm;
  

В useValidatedForm функции проблема, с которой я сталкиваюсь, заключается в том, что когда я отправляю форму и вызывается эта функция, initialState правильно, она возвращает first name , она используется в качестве начального значения для, state но state вернется в виде пустой строки и будет делать это до тех пор, пока я не введу входные данные, а затем она правильно обновится. Итак, я не совсем уверен, как заставить эту проверку работать, поскольку она зависит от state значения и обновляет state значение с помощью setState ? Любая помощь была бы высоко оценена.

Ответ №1:

При отладке вашего кода вам действительно нужно идти шаг за шагом:

  • Начните с того места, где он неисправен -> validateProfileForm -> откуда он берется?

  • проверка, возвращаемая вашим хуком -> откуда validate получает свои данные?

  • переменная состояния верхней области видимости -> откуда она берется?

  • Строка 147 useState в этой изолированной среде -> откуда useState получает это значение?

  • initialState -> имеет ли initialState правильное значение?

  • Да -> почему useState не использует initialState ?

  • О, подождите, useState использует initialParameters ТОЛЬКО при первом вызове, поэтому я не могу использовать параметр для повторной инициализации значений. Поскольку вы задаете initialState позже, первое значение, которое было у useState, теперь застряло в состоянии, если вы не используете setState

Итак, теперь ваши варианты таковы: используйте setState только при изменении initialState (потому что, если вы будете делать это каждый раз, вы будете придерживаться начальных значений), но поскольку ваш stateFactory всегда возвращает новый объект, я не рекомендую этого (вам пришлось бы глубоко сравнивать)

или

Предоставьте обязательный метод для повторной инициализации состояния, таким образом, вам не придется иметь дело с возможно раздражающей системой различий.

Кроме того, причина, по которой вы вообще получаете «first name» во входных данных, в первую очередь, заключается в том, что вы задаете его значение с помощью value={profileFormInitialState.firstName} , когда ваша форма должна быть той, которая обрабатывает это за вас.