<commit_before>// @flow
import { useState, useEffect, useRef } from 'react';
import useRelayEnvironment from './useRelayEnvironment';
import {
  commitMutation,
  type Disposable,
  type RelayMutationConfig,
  type GraphQLTaggedNode,
  type UploadableMap,
  type SelectorStoreUpdater,
} from 'react-relay';

// Subset of react-relay MutationConfig type.
// Variables and event handlers are added in useMutation hook.
type Config = {|
  configs?: Array<RelayMutationConfig>,
  mutation: GraphQLTaggedNode,
  uploadables?: UploadableMap,
  optimisticUpdater?: ?SelectorStoreUpdater,
  optimisticResponse?: Object,
  updater?: ?SelectorStoreUpdater,
|};

type Input<Mutation> = $ElementType<
  $ElementType<Mutation, 'variables'>,
  'input',
>;

// Errors parametric type is carefully crafted, let me explain why.
// Errors are defined in server model. Properly generated Flow looks like this:
// From server/api/__generated__/api.graphql.js:
// export type AuthErrors = {|
//   email?: EmailError,
//   password?: PasswordError,
// |}
// It's the shape that validateAuth returns. But AuthMutation looks like that:
// +errors: ?{|
//   +email: ?EmailError,
//   +password: ?PasswordError,
// |},
// Note email and password props are not optional. That's correct, because we
// fetch both. But for compatibility with the rest of the code, we have to
// $Shape, NonMaybeType before, etc. and result must be made nullable again.
type Errors<Mutation, Name> = ?$Shape<
  $NonMaybeType<
    $ElementType<
      $NonMaybeType<$ElementType<$ElementType<Mutation, 'response'>, Name>>,
      'errors',
    >,
  >,
>;

type Commit<Mutation, Name> = (
  input: Input<Mutation>,
  onSuccess: ?(
    $NonMaybeType<$ElementType<$ElementType<Mutation, 'response'>, Name>>,
  ) => void,
) => void;

// ': Object' is must, otherwise https://github.com/facebook/flow/issues/7191.
// Maybe Name and name can be defined once, but I don't know how.
export default function useMutation<Mutation: Object, Name: string>(
  config: Config,
  name: Name,
  validate?: (Input<Mutation>) => Errors<Mutation, Name>,
): [Commit<Mutation, Name>, boolean, Errors<Mutation, Name>] {
  const environment = useRelayEnvironment();
  const disposableRef = useRef<Disposable>(null);
  const [errors, setErrors] = useState<Errors<Mutation, Name>>(null);
  const [pending, setPending] = useState(false);

  useEffect(() => {
    return () => {
      if (disposableRef.current) disposableRef.current.dispose();
    };
  }, []);

  function commit(input, onSuccess) {
    if (validate) {
      const errors = validate(input);
      setErrors(errors);
      if (errors != null) return;
    }
    setPending(true);
    if (disposableRef.current) disposableRef.current.dispose();
    // Mixed, because I don't know how to type whole response properly.
    disposableRef.current = commitMutation<mixed>(environment, {
      ...config,
      variables: { input },
      onCompleted(response /* , payloadErrors */) {
        setPending(false);
        // Flow type refinement because of mixed.
        if (typeof response !== 'object' || response == null) return;
        const data = response[name];
        if (typeof data !== 'object' || data == null) return;
        const { errors } = data;
        setErrors(errors);
        if (errors != null) return;
        if (onSuccess) onSuccess(data);
      },
      onError(/* error */) {
        setPending(false);
      },
    });
  }

  return [commit, pending, errors];
}<commit_msg>Refactor useMutation.js<commit_after>// @flow
import { useState, useEffect, useRef } from 'react';
import useRelayEnvironment from './useRelayEnvironment';
import {
  commitMutation,
  type Disposable,
  type RelayMutationConfig,
  type GraphQLTaggedNode,
  type UploadableMap,
  type SelectorStoreUpdater,
} from 'react-relay';

// Subset of react-relay MutationConfig type.
// Variables and event handlers are added in useMutation hook.
type Config = {|
  configs?: Array<RelayMutationConfig>,
  mutation: GraphQLTaggedNode,
  uploadables?: UploadableMap,
  optimisticUpdater?: ?SelectorStoreUpdater,
  optimisticResponse?: Object,
  updater?: ?SelectorStoreUpdater,
|};

type Input<Mutation> = $ElementType<
  $ElementType<Mutation, 'variables'>,
  'input',
>;

// Errors parametric type is carefully crafted, let me explain why.
// Errors are defined in server model. Properly generated Flow looks like this:
// From server/api/__generated__/api.graphql.js:
// export type AuthErrors = {|
//   email?: EmailError,
//   password?: PasswordError,
// |}
// It's the shape that validateAuth returns. But AuthMutation looks like that:
// +errors: ?{|
//   +email: ?EmailError,
//   +password: ?PasswordError,
// |},
// Note email and password props are not optional. That's correct, because we
// fetch both. But for compatibility with the rest of the code, we have to
// $Shape, NonMaybeType before, etc. and result must be made nullable again.
type Errors<Mutation, Name> = ?$Shape<
  $NonMaybeType<
    $ElementType<
      $NonMaybeType<$ElementType<$ElementType<Mutation, 'response'>, Name>>,
      'errors',
    >,
  >,
>;

type Commit<Mutation, Name> = (
  input: Input<Mutation>,
  onSuccess: ?(
    $NonMaybeType<$ElementType<$ElementType<Mutation, 'response'>, Name>>,
  ) => void,
) => void;

// ': Object' is must, otherwise https://github.com/facebook/flow/issues/7191.
// Maybe Name and name can be defined once, but I don't know how.
export default function useMutation<Mutation: Object, Name: string>(
  config: Config,
  name: Name,
  validate?: (Input<Mutation>) => Errors<Mutation, Name>,
): [Commit<Mutation, Name>, boolean, Errors<Mutation, Name>] {
  const environment = useRelayEnvironment();
  const disposableRef = useRef<Disposable>(null);
  const [errors, setErrors] = useState<Errors<Mutation, Name>>(null);
  const [pending, setPending] = useState(false);

  useEffect(() => {
    return () => {
      if (disposableRef.current) disposableRef.current.dispose();
    };
  }, []);

  function createOnCompleted(onSuccess) {
    return function onCompleted(response /* , payloadErrors */) {
      setPending(false);
      // Flow type refinement because of mixed.
      if (typeof response !== 'object' || response == null) return;
      const data = response[name];
      if (typeof data !== 'object' || data == null) return;
      const { errors } = data;
      setErrors(errors);
      if (errors != null) return;
      if (onSuccess) onSuccess(data);
    };
  }

  function commit(input, onSuccess) {
    if (validate) {
      const errors = validate(input);
      setErrors(errors);
      if (errors != null) return;
    }
    setPending(true);
    if (disposableRef.current) disposableRef.current.dispose();
    // Mixed, because I don't know how to type whole response properly.
    disposableRef.current = commitMutation<mixed>(environment, {
      ...config,
      variables: { input },
      onCompleted: createOnCompleted(onSuccess),
      onError(/* error */) {
        setPending(false);
      },
    });
  }

  return [commit, pending, errors];
}
{'fraction_non_alphanumeric': 0.13539898132427844, 'fraction_numerical': 0.0011318619128466328, 'mean_word_length': 3.669088507265522, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 63, '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': '6773705', 'n_tokens_mistral': 2162, 'n_tokens_neox': 2127, 'n_words': 607}