<commit_before>import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
import { FormContext } from './FormContext';

const defaultMessages = {
  invalid: 'invalid',
  required: 'required',
};
const defaultValue = {};
const defaultTouched = {};
const defaultValidationResults = {
  errors: {},
  infos: {},
};

// validations is an array from Object.entries()
const validate = (validations, value, omitValid) => {
  const nextErrors = {};
  const nextInfos = {};
  validations.forEach(([name, validation]) => {
    if (!omitValid) {
      nextErrors[name] = undefined;
      nextInfos[name] = undefined;
    }
    const result = validation(value[name], value);
    // typeof error === 'object' is implied for both cases of error with
    // a status message and for an error object that is a react node
    if (typeof result === 'object') {
      if (result.status === 'info') {
        nextInfos[name] = result.message;
      } else {
        nextErrors[name] = result.message || result; // could be a node
      }
    } else if (typeof result === 'string') {
      nextErrors[name] = result;
    }
  });
  return [nextErrors, nextInfos];
};

const Form = forwardRef(
  (
    {
      children,
      errors: errorsProp = defaultValidationResults.errors,
      infos: infosProp = defaultValidationResults.infos,
      messages = defaultMessages,
      onChange,
      onReset,
      onSubmit,
      onValidate,
      validate: validateOn = 'submit',
      value: valueProp,
      ...rest
    },
    ref,
  ) => {
    const [valueState, setValueState] = useState(valueProp || defaultValue);
    const value = useMemo(() => valueProp || valueState, [
      valueProp,
      valueState,
    ]);
    const [touched, setTouched] = useState(defaultTouched);
    const [validationResults, setValidationResults] = useState(
      defaultValidationResults,
    );
    const [requiredFields, setRequiredFields] = useState([]);

    // when onBlur input validation is triggered, we need to complete any
    // potential click events before running the onBlur validation.
    // otherwise, click events like reset, etc. may not be registered.
    // for a detailed scenario/discussion,
    // see: https://github.com/grommet/grommet/issues/4863
    // the value of pendingValidation is the name of the FormField
    // awaiting validation.
    const [pendingValidation, setPendingValidation] = useState(undefined);

    useEffect(() => {
      setPendingValidation(undefined);
      setValidationResults({ errors: errorsProp, infos: infosProp });
    }, [errorsProp, infosProp]);
    const validations = useRef({});

    // Currently, onBlur validation will trigger after a timeout of 120ms.
    useEffect(() => {
      const timer = setTimeout(() => {
        if (pendingValidation) {
          // run validations on the pending one and any other touched fields
          const [validatedErrors, validatedInfos] = validate(
            Object.entries(validations.current).filter(
              ([n]) => touched[n] || pendingValidation.includes(n),
            ),
            value,
          );
          setPendingValidation(undefined);

          setRequiredFields(prevRequiredFields =>
            prevRequiredFields.filter(n =>
              Object.keys(validations.current).includes(n),
            ),
          );

          setValidationResults(prevValidationResults => {
            const nextErrors = {
              ...prevValidationResults.errors,
              ...validatedErrors,
            };
            const nextInfos = {
              ...prevValidationResults.infos,
              ...validatedInfos,
            };

            // Remove any errors or infos that we don't have any validations
            // for anymore. This can occur when fields are dynamically removed.
            Object.keys(nextErrors)
              .filter(
                n => !validations.current[n] || nextErrors[n] === undefined,
              )
              .map(n => delete nextErrors[n]);
            Object.keys(nextInfos)
              .filter(
                n => !validations.current[n] || nextInfos[n] === undefined,
              )
              .map(n => delete nextInfos[n]);

            let valid = false;

            valid = requiredFields.every(
              field =>
                value[field] && (value[field] !== '' || value[field] !== false),
            );

            if (Object.keys(nextErrors).length > 0) valid = false;

            // keep any previous errors and infos for untouched keys,
            // these may have come from a submit
            const nextValidationResults = {
              errors: nextErrors,
              infos: nextInfos,
              valid,
            };
            if (onValidate) onValidate(nextValidationResults);
            return nextValidationResults;
          });
        }
        // a timeout is needed to ensure that a click event (like one on a reset
        // button) completes prior to running the validation. without a timeout,
        // the blur will always complete and trigger a validation prematurely
        // The following values have been empirically tested, but 120 was
        // selected because it is the largest value
        // Chrome: 100, Safari: 120, Firefox: 80<commit_msg>Before the state gets updated validation is being run (#5035)<commit_after>import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
import { FormContext } from './FormContext';

const defaultMessages = {
  invalid: 'invalid',
  required: 'required',
};
const defaultValue = {};
const defaultTouched = {};
const defaultValidationResults = {
  errors: {},
  infos: {},
};

// validations is an array from Object.entries()
const validate = (validations, value, omitValid) => {
  const nextErrors = {};
  const nextInfos = {};
  validations.forEach(([name, validation]) => {
    if (!omitValid) {
      nextErrors[name] = undefined;
      nextInfos[name] = undefined;
    }
    const result = validation(value[name], value);
    // typeof error === 'object' is implied for both cases of error with
    // a status message and for an error object that is a react node
    if (typeof result === 'object') {
      if (result.status === 'info') {
        nextInfos[name] = result.message;
      } else {
        nextErrors[name] = result.message || result; // could be a node
      }
    } else if (typeof result === 'string') {
      nextErrors[name] = result;
    }
  });
  return [nextErrors, nextInfos];
};

const Form = forwardRef(
  (
    {
      children,
      errors: errorsProp = defaultValidationResults.errors,
      infos: infosProp = defaultValidationResults.infos,
      messages = defaultMessages,
      onChange,
      onReset,
      onSubmit,
      onValidate,
      validate: validateOn = 'submit',
      value: valueProp,
      ...rest
    },
    ref,
  ) => {
    const [valueState, setValueState] = useState(valueProp || defaultValue);
    const value = useMemo(() => valueProp || valueState, [
      valueProp,
      valueState,
    ]);
    const [touched, setTouched] = useState(defaultTouched);
    const [validationResults, setValidationResults] = useState(
      defaultValidationResults,
    );
    const [requiredFields, setRequiredFields] = useState([]);

    // when onBlur input validation is triggered, we need to complete any
    // potential click events before running the onBlur validation.
    // otherwise, click events like reset, etc. may not be registered.
    // for a detailed scenario/discussion,
    // see: https://github.com/grommet/grommet/issues/4863
    // the value of pendingValidation is the name of the FormField
    // awaiting validation.
    const [pendingValidation, setPendingValidation] = useState(undefined);

    useEffect(() => {
      setPendingValidation(undefined);
      setValidationResults({ errors: errorsProp, infos: infosProp });
    }, [errorsProp, infosProp]);
    const validations = useRef({});

    // Currently, onBlur validation will trigger after a timeout of 120ms.
    useEffect(() => {
      const timer = setTimeout(() => {
        if (pendingValidation) {
          // run validations on the pending one and any other touched fields
          const [validatedErrors, validatedInfos] = validate(
            Object.entries(validations.current).filter(
              ([n]) => touched[n] || pendingValidation.includes(n),
            ),
            value,
          );
          setPendingValidation(undefined);

          setValidationResults(prevValidationResults => {
            const nextErrors = {
              ...prevValidationResults.errors,
              ...validatedErrors,
            };
            const nextInfos = {
              ...prevValidationResults.infos,
              ...validatedInfos,
            };

            // Remove any errors or infos that we don't have any validations
            // for anymore. This can occur when fields are dynamically removed.
            Object.keys(nextErrors)
              .filter(
                n => !validations.current[n] || nextErrors[n] === undefined,
              )
              .map(n => delete nextErrors[n]);
            Object.keys(nextInfos)
              .filter(
                n => !validations.current[n] || nextInfos[n] === undefined,
              )
              .map(n => delete nextInfos[n]);

            let valid = false;

            valid = requiredFields
              .filter(n => Object.keys(validations.current).includes(n))
              .every(
                field =>
                  value[field] &&
                  (value[field] !== '' || value[field] !== false),
              );

            if (Object.keys(nextErrors).length > 0) valid = false;

            // keep any previous errors and infos for untouched keys,
            // these may have come from a submit
            const nextValidationResults = {
              errors: nextErrors,
              infos: nextInfos,
              valid,
            };
            if (onValidate) onValidate(nextValidationResults);
            return nextValidationResults;
          });
        }
        // a timeout is needed to ensure that a click event (like one on a reset
        // button) completes prior to running the validation. without a timeout,
        // the blur will always complete and trigger a validation prematurely
        // The following values have been empirically tested, but 120 was
        // selected because it is the largest value
        // Chrome: 100, Safari: 120, Firefox: 80
{'fraction_non_alphanumeric': 0.10849772382397573, 'fraction_numerical': 0.003983308042488619, 'mean_word_length': 2.1685697115384617, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 36, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1660954', 'n_tokens_mistral': 2740, 'n_tokens_neox': 2662, 'n_words': 818}