re: No more tears, handling Forms in React using Formik, part I VIEW POST

FULL DISCUSSION
 

Now that React has introduced hooks, don't you think it's definitely easier to just handle validation, local state and events with custom, reusable hooks? Perhaps I'd be more interested in what it does with schemas and components.

 

Well, I think it's gonna take a long time before Hooks become a thing everyone uses and a lot of it.. I've dug some into Hooks as you can see from one of my other articles.. Also Hook is a new thinng and added to a late version of React so a lot of old React projects wont be able to use it until they upgrade. It's an interesting topic though.. I'm gonna look into it Forms + Hooks I mean :) Thanks for the comment

 

I definitely like the render props version over the higher-order component (HOC) version. I understand HOCs, but they always seem to hurt my brain.

I know Jared Palmer, creator of Formik, is working on a hooks implementation of Formik. Not sure if it's finished or in beta.

Ah cool, looking forward to the hooks version :)

Looks like the issue is still open.

Hooks Rewrite #1046

import React, {
  useContext,
  useState,
  createContext,
  createElement,
} from 'react';

const FormikContext = createContext(null);

export function Formik({
  initialValues,
  validate,
  onSubmit,
  validateOnBlur = true,
  validateOnChange = true,
  ...props
}) {
  const [values, updateValues] = useState(initialValues);
  const [errors, updateErrors] = useState({});
  const [touched, updateTouched] = useState({});
  const [submitAttemptCount, updateSubmitAttemptCount] = useState(0);
  const [isSubmitting, updateIsSubmitting] = useState(false);
  const [isValidating, updateIsValidating] = useState(false);

  function validateForm(vals = values) {
    updateIsValidating(true);
    return Promise.resolve(validate ? validate(vals) : {})
      .then(x => x, e => e)
      .then(e => {
        // return or take a callback?
        updateErrors(e);
        updateIsValidating(false);
      });
  }

  function getFieldProps(name, type) {
    return {
      value:
        type === 'radio' || type === 'checkbox'
          ? undefined // React uses checked={} for these inputs
          : values[name],
      onChange(e) {
        e.persist();
        updateValues(
          prevValues => ({ ...prevValues, [name]: e.target.value }),
          () => {
            if (validateOnChange && validate) {
              validateForm();
            }
          }
        );
      },
      onBlur() {
        updateTouched(
          prevTouched => ({ ...prevTouched, [name]: true }),
          () => {
            if (validateOnChange && validate) {
              validateForm();
            }
          }
        );
      },
    };
  }

  async function submitForm() {
    updateTouched(setNestedObjectValues(values, true));
    updateIsSubmitting(true);
    updateSubmitAttemptCount(prev => prev++);
    try {
      await validateForm();
      const errors = await onSubmit(values);
      if (errors) {
        updateErrors(errors);
      }
      updateIsSubmitting(false);
    } catch (errors) {
      updateErrors(errors);
      updateIsSubmitting(false);
    }
  }

  function handleSubmit(e) {
    e.preventDefault();
    submitForm();
  }

  const ctx = {
    values,
    updateValues,
    errors,
    updateErrors,
    touched,
    updateTouched,
    submitAttemptCount,
    updateSubmitAttemptCount,
    isSubmitting,
    updateIsSubmitting,
    isValidating,
    validateOnChange,
    validateOnBlur,
    getFieldProps,
    handleSubmit,
    submitForm,
  };

  return (
    <FormikContext.Provider value={ctx}>
      {props.children}
    </FormikContext.Provider>
  );
}

export function Form(props) {
  const formik = useContext(FormikContext);
  return <form onSubmit={formik.handleSubmit} {...props} />;
}

export function Debug(props) {
  const formik = useContext(FormikContext);
  console.log({ formik });
  return null;
}

// Backwards compatible Field
export function Field({
  component = 'input',
  render,
  children,
  name,
  ...props
}) {
  const { getFieldProps } = useContext(FormikContext);

  const fieldProps = {
    ...getFieldProps(name, props.type),
    ...props,
  };

  if (children && typeof children === 'function') {
    return children(fieldProps);
  }

  if (render && typeof render === 'function') {
    return render(fieldProps);
  }

  return createElement(component, fieldProps, children);
}

/** @private is the given object an Object? */
export const isObject = obj => obj !== null && typeof obj === 'object';

/**
 * Recursively a set the same value for all keys and arrays nested object, cloning
 * @param object
 * @param value
 * @param visited
 * @param response
 */
export function setNestedObjectValues(
  object,
  value,
  visited = new WeakMap(),
  response = {}
): T {
  for (let k of Object.keys(object)) {
    const val = object[k];
    if (isObject(val)) {
      if (!visited.get(val)) {
        visited.set(val, true);
        // In order to keep array values consistent for both dot path  and
        // bracket syntax, we need to check if this is an array so that
        // this will output  { friends: [true] } and not { friends: { "0": true } }
        response[k] = Array.isArray(val) ? [] : {};
        setNestedObjectValues(val, value, visited, response[k]);
      }
    } else {
      response[k] = value;
    }
  }

  return response;
}

Looks like there's an alpha release for Formik with hooks now. 🎉

code of conduct - report abuse