最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Re-appearing old values in React TS when applying custom validation on <Input> field from 're

programmeradmin4浏览0评论

I'm having issue with re-appearing old values in React TS. I'm initially providing my code so you can check it as well:

 const initialState = {
        name: '',
      };

  const [registeredValidations, setRegisteredValidations] = useState({} as RegisteredValidationMap);
      const [formData, setFormData] = useState({
        ...initialState,
        name: requestStore.formData.name[0] || initialState.name
      });
      

And here is the call of my custom implemented validation for input field and the validation:

      <ValidatedInput
        name="name"
        value={formData.name}
        onChange={handleOnChange}
        validations={[GenericInputValidation]}
        isOptional={!fieldsRequired}
        setRegisteredValidations={setRegisteredValidations}
        registeredValidations={registeredValidations}
      />
   <Row>
        <Navigation handleOnNextClick={handleOnNextClick} registeredValidations={registeredValidations} />
      </Row>

the onChange listener implementation:

  const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    
    setFormData(prevState => ({
      ...prevState,
      [name]: value,
    }));
  };

the GenericInputValidation:

export class GenericInputValidation extends Validation {
  // eslint-disable-next-line @typescript-eslint/require-await
  static async validate(props: ValidationProps): Promise<string[]> {
    const errors = [];

    if (props.newValue === '' && !props.isOptional) {
      errors.push(translate('error.missing-value'));
    }

    return errors;
  }
}
export default GenericInputValidation;

and now the ValidatedInput field implementation:

export const ValidatedInput = (props: ValidatedInputProps) => {
  const [errors, setErrors] = useState([...(props.externalErrors || [])]);

  const errorMessages = filterMessagesByType(errors, ERROR_TEMPLATES) || [];
  const warningMessages = filterMessagesByType(errors, WARNING_TEMPLATES) || [];
  
  let css = '';
  if(errorMessages.length !== 0){
    css= 'validation-failed'
  }else if(warningMessages.length !== 0){
    css = 'validation-warning'
  }

  const DOMprops = getDomProps(props);
  registerValidationComponent(props, errorMessages.length > 0 ? errorMessages : warningMessages, setErrors);

  return (
    <div>
      <Input
        {...DOMprops}
        onBlur={e => {
          generateBlur<HTMLInputElement>(props, errors, setErrors)(e);
        }}
        id={props.id || props.name}
        className={css}
        key={props.id + errors.length}
        readOnly={props.readOnly}
        type={props.type}
        maxLength={props.maxLength}
      />
      {errors.length !== 0 && (
        <Tooltip anchorSelect={`#${props.id || props.name}`} place="top">
          {getErrorsForTooltip(errors)}
        </Tooltip>
      )}
    </div>
  );
};

export default ValidatedInput;

and the Navigation implementation:

    const navigate = useNavigate();
      const dispatch = useDispatch();
      const defaultBack = () => {
        navigate(-1);
      };
      const [warningAcknowledged, setWarningAcknowledged] = useState(false);
      const handleBackClick = () => {
        dispatch(setWarningsAcknowledged(false));
        setWarningAcknowledged(false);
        if (props.handleOnBackClick) {
          props.handleOnBackClick();
        } else {
          defaultBack();
        }
      };
    
      useEffect(() => {
        setWarningAcknowledged(false);
      }, [JSON.stringify(props.registeredValidations)]);
    
      const nextFunction = async () => {
        const foundErrors = [];
        for (const registered of Object.values(props.registeredValidations)) {
          const { value, validations, customValidations, errors, setErrors } = registered;
          const validationProps: ValidationProps = {
            name: registered.name,
            newValue: value,
            validations,
            customValidations,
            errors,
            setErrors,
            isOptional: registered.isOptional,
            maxLength: registered.maxLength,
          };
    
          foundErrors.push(...(await runValidations(validationProps)));
        }
    
        const errorMessages = filterMessagesByType(foundErrors, ERROR_TEMPLATES);
        const warningMessagesFiltered = filterMessagesByType(foundErrors, WARNING_TEMPLATES);
        const infoMessages = filterMessagesByType(foundErrors, INFO_TEMPLATES);
        const hintMessages = filterMessagesByType(foundErrors, HINT_TEMPLATES);
        const warningMessagesAndInfo = warningMessagesFiltered.concat(infoMessages);
        const warningMessages = warningMessagesAndInfo.concat(hintMessages);
    
        if (errorMessages.length > 0) {
          return;
        } else if (warningMessages.length > 0 && !warningAcknowledged) {
          setWarningAcknowledged(true);
          return;
        }
    
        props.handleOnNextClick();
return (
    <Container className="mt-4">
      <Row>
        <Col xs={6}>
          {!props.hideBack && (
            <Button name={'zurück' + (props.nameSuffix || '')} id={'back-button' + (props.nameSuffix || '')} onClick={handleBackClick}>
              <FontAwesomeIcon icon={faArrowLeft}></FontAwesomeIcon> <Translate contentKey="generic.back" />
            </Button>
          )}
        </Col>

        <Col xs={6} className="next-button-col">
          {!props.hideNext && (
            <Button
              name={'weiter' + (props.nameSuffix || '')}
              id={'next-button' + (props.nameSuffix || '')}
              onClick={() => {
                nextFunction();
              }}
            >
              <Translate contentKey="generic.next" /> <FontAwesomeIcon icon={faArrowRight}></FontAwesomeIcon>
            </Button>
          )}
        </Col>
      </Row>
    </Container>
  );

Now when everything is done and call the ValidatedInput in a page, and save the values ones(which happens onNextClick) then go back on that page I got the values from the request store shown in the Input field which is prefectly fine.And now when I want to remove the input(just for triggering the validation) and click next - the validation is triggered which is ok, but the value in the field returns the old one, instead to hold on the new one and show that this field is incorrect.

Example:

Field has value: "test123" I change to: "" Clicking Next button triggers the validation and instead of staying the value to "" it returns the "test123"

Any ideas how to fix it ?

I'm having issue with re-appearing old values in React TS. I'm initially providing my code so you can check it as well:

 const initialState = {
        name: '',
      };

  const [registeredValidations, setRegisteredValidations] = useState({} as RegisteredValidationMap);
      const [formData, setFormData] = useState({
        ...initialState,
        name: requestStore.formData.name[0] || initialState.name
      });
      

And here is the call of my custom implemented validation for input field and the validation:

      <ValidatedInput
        name="name"
        value={formData.name}
        onChange={handleOnChange}
        validations={[GenericInputValidation]}
        isOptional={!fieldsRequired}
        setRegisteredValidations={setRegisteredValidations}
        registeredValidations={registeredValidations}
      />
   <Row>
        <Navigation handleOnNextClick={handleOnNextClick} registeredValidations={registeredValidations} />
      </Row>

the onChange listener implementation:

  const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    
    setFormData(prevState => ({
      ...prevState,
      [name]: value,
    }));
  };

the GenericInputValidation:

export class GenericInputValidation extends Validation {
  // eslint-disable-next-line @typescript-eslint/require-await
  static async validate(props: ValidationProps): Promise<string[]> {
    const errors = [];

    if (props.newValue === '' && !props.isOptional) {
      errors.push(translate('error.missing-value'));
    }

    return errors;
  }
}
export default GenericInputValidation;

and now the ValidatedInput field implementation:

export const ValidatedInput = (props: ValidatedInputProps) => {
  const [errors, setErrors] = useState([...(props.externalErrors || [])]);

  const errorMessages = filterMessagesByType(errors, ERROR_TEMPLATES) || [];
  const warningMessages = filterMessagesByType(errors, WARNING_TEMPLATES) || [];
  
  let css = '';
  if(errorMessages.length !== 0){
    css= 'validation-failed'
  }else if(warningMessages.length !== 0){
    css = 'validation-warning'
  }

  const DOMprops = getDomProps(props);
  registerValidationComponent(props, errorMessages.length > 0 ? errorMessages : warningMessages, setErrors);

  return (
    <div>
      <Input
        {...DOMprops}
        onBlur={e => {
          generateBlur<HTMLInputElement>(props, errors, setErrors)(e);
        }}
        id={props.id || props.name}
        className={css}
        key={props.id + errors.length}
        readOnly={props.readOnly}
        type={props.type}
        maxLength={props.maxLength}
      />
      {errors.length !== 0 && (
        <Tooltip anchorSelect={`#${props.id || props.name}`} place="top">
          {getErrorsForTooltip(errors)}
        </Tooltip>
      )}
    </div>
  );
};

export default ValidatedInput;

and the Navigation implementation:

    const navigate = useNavigate();
      const dispatch = useDispatch();
      const defaultBack = () => {
        navigate(-1);
      };
      const [warningAcknowledged, setWarningAcknowledged] = useState(false);
      const handleBackClick = () => {
        dispatch(setWarningsAcknowledged(false));
        setWarningAcknowledged(false);
        if (props.handleOnBackClick) {
          props.handleOnBackClick();
        } else {
          defaultBack();
        }
      };
    
      useEffect(() => {
        setWarningAcknowledged(false);
      }, [JSON.stringify(props.registeredValidations)]);
    
      const nextFunction = async () => {
        const foundErrors = [];
        for (const registered of Object.values(props.registeredValidations)) {
          const { value, validations, customValidations, errors, setErrors } = registered;
          const validationProps: ValidationProps = {
            name: registered.name,
            newValue: value,
            validations,
            customValidations,
            errors,
            setErrors,
            isOptional: registered.isOptional,
            maxLength: registered.maxLength,
          };
    
          foundErrors.push(...(await runValidations(validationProps)));
        }
    
        const errorMessages = filterMessagesByType(foundErrors, ERROR_TEMPLATES);
        const warningMessagesFiltered = filterMessagesByType(foundErrors, WARNING_TEMPLATES);
        const infoMessages = filterMessagesByType(foundErrors, INFO_TEMPLATES);
        const hintMessages = filterMessagesByType(foundErrors, HINT_TEMPLATES);
        const warningMessagesAndInfo = warningMessagesFiltered.concat(infoMessages);
        const warningMessages = warningMessagesAndInfo.concat(hintMessages);
    
        if (errorMessages.length > 0) {
          return;
        } else if (warningMessages.length > 0 && !warningAcknowledged) {
          setWarningAcknowledged(true);
          return;
        }
    
        props.handleOnNextClick();
return (
    <Container className="mt-4">
      <Row>
        <Col xs={6}>
          {!props.hideBack && (
            <Button name={'zurück' + (props.nameSuffix || '')} id={'back-button' + (props.nameSuffix || '')} onClick={handleBackClick}>
              <FontAwesomeIcon icon={faArrowLeft}></FontAwesomeIcon> <Translate contentKey="generic.back" />
            </Button>
          )}
        </Col>

        <Col xs={6} className="next-button-col">
          {!props.hideNext && (
            <Button
              name={'weiter' + (props.nameSuffix || '')}
              id={'next-button' + (props.nameSuffix || '')}
              onClick={() => {
                nextFunction();
              }}
            >
              <Translate contentKey="generic.next" /> <FontAwesomeIcon icon={faArrowRight}></FontAwesomeIcon>
            </Button>
          )}
        </Col>
      </Row>
    </Container>
  );

Now when everything is done and call the ValidatedInput in a page, and save the values ones(which happens onNextClick) then go back on that page I got the values from the request store shown in the Input field which is prefectly fine.And now when I want to remove the input(just for triggering the validation) and click next - the validation is triggered which is ok, but the value in the field returns the old one, instead to hold on the new one and show that this field is incorrect.

Example:

Field has value: "test123" I change to: "" Clicking Next button triggers the validation and instead of staying the value to "" it returns the "test123"

Any ideas how to fix it ?

Share Improve this question asked 9 hours ago Dilyan GalabovDilyan Galabov 616 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 0

When the error state changes (for example, when validation is triggered after you clear the field), the key changes. React then unmounts the existing input and remounts a new one. This remounting resets the input’s internal state and causes it to revert to the initial prop value (which in your case is still “test123”) rather than preserving the empty string you set.

Remove the key attribute from your input.

By not forcing a remount on every validation error update, the input remains a controlled component, so the displayed value will correctly reflect the state in formData.

For example, change your code from:

<Input
  {...DOMprops}
  onBlur={e => {
    generateBlur<HTMLInputElement>(props, errors, setErrors)(e);
  }}
  id={props.id || props.name}
  className={css}
  key={props.id + errors.length}
  readOnly={props.readOnly}
  type={props.type}
  maxLength={props.maxLength}
/>

to:

<Input
  {...DOMprops}
  onBlur={e => {
    generateBlur<HTMLInputElement>(props, errors, setErrors)(e);
  }}
  id={props.id || props.name}
  className={css}
  readOnly={props.readOnly}
  type={props.type}
  maxLength={props.maxLength}
/>

Can you create a code sandbox to showcase your issue? I assume that the problem is coming from the way you modify formData, I used to get some similar problem when changing property of an object. Maybe try to move the initialState out of the component and console.log registered.name to see it has the data you need? For now, I don't have much info from your code so I'm not sure if you accidentally modified/re-assigned it incorrectly. Also, a suggestion for the Input component I think that you should keep key stable, key={props.id + errors.length} using this may cause your component to have some unnecessary re-rendering.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论