streamich / react-use

React Hooks — 👍
http://streamich.github.io/react-use
The Unlicense
41.54k stars 3.13k forks source link

useForm #63

Open revskill10 opened 5 years ago

revskill10 commented 5 years ago

Any idea on useForm with (async) validation as a hook ?

streamich commented 5 years ago

What API do you imagine for that?

revskill10 commented 5 years ago

@streamich I'm not sure about validation part, but for form, the api should be simple, based on standard html input types.

const { useCheckbox, useText, useImage, useRadio, useButton, useFile, 
useHidden, usePassword, useReset, useSubmit, useButton } = useForm()
const { value, setValue } = useCheckbox(defaultValue)
...
streamich commented 5 years ago

Maybe something like this:

const {useForm, useText, usePassword} = formHooks('my-form');

const Demo = () => {
  const state = useForm();
  const inputEmail = useText('email', 'Enter your e-mail');
  const inputPassword = usePassword('password', 'Password...');
  const onSubmit = useCallback(() => {
    // ...
  });

  return (
    <form onSubmit={onSubmit}>
      <input {...inputEmail} />
      <input {...inputPassword} />
      <button type="submit">Submit</button>
    </form>
  );
};

I don't know, it looks like there is a lot to think about.

revskill10 commented 5 years ago

@streamich I'm testing this code:

import { useState } from 'react'
import Validator from 'fastest-validator'

export const useForm = (initialState, schema) => {
  const [values, setValues] = useState(initialState)
  const [errors, setErrors] = useState({})
  const v = new Validator();
  const check = v.compile(schema);

  const updateValues = (value, name) => {
    setValues({ ...values, [name]: value})
  }

  const onChange = (value, name) => {
    if (value.target) {
      const e = value
      updateValues(e.target.name, e.target.value)
    } else {
      updateValues(name, value)
    }
  }

  const onSubmit = (e) => {
    e.preventDefault()
    const errors = check(values)
    if (errors.length > 0) {
      setErrors(errors)
    } else {
      setErrors([])
    }
  }

  return {
    values,
    errors,
    onChange,
    onSubmit,
  }
} 

export default useForm

Usage like this

import { useForm } from 'lib/hooks/form'

const initialState = {
    firstName: "Truong",
    lastName: "Dung",
    age: 30,
  }

  const schema = {
    firstName: { type: "string" },
    lastName: { type: "string", optional: true },
    age: { type: "number", optional: true },
  }

const TestForm = () => {

  const {
    values,
    errors,
    onChange,
    onSubmit,
  } = useForm(initialState, schema)

  return (
    <div>
      <h1>New User</h1>
      <div>{JSON.stringify(errors)}</div>
      <form onSubmit={onSubmit} >
        <label>First name</label>
        <input type="text" name="firstName" onChange={onChange} value={values.firstName} />
        <label>Last name</label>
        <input type="text" name="lastName" onChange={onChange} value={values.lastName} />
        <label>Age</label>
        <input type="text" name="age" onChange={onChange} value={values.age} />
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default TestForm

There's an error that prevent field to change its value.

cloudever commented 5 years ago

Formik will have form hooks, I think no need here

streamich commented 5 years ago

Here is a nice simple interface:

brianle1301 commented 5 years ago

@streamich @revskill10 I prefer this API, which provides all the validation functionalities and uses validator under the hood.

const [
    { emai: { value, errorText, isValid } },
    handleFieldChange,
    handleFieldBlur,
    handleFieldFocus,
    validateFieldAfterSubmit,
  ] = useForm({
    email: {
      value: '',
      validationRules: [
        {
          method: isEmpty,
          validWhen: false,
          errorText: 'This field should not be left empty!',
        },
        {
          method: isEmail,
          errorText: 'This field should contain an email!',
        },
      ],
    },
  });

Then just plug all the returned functions into an input

<input onChange={handleFieldChange} onFocus={handleFieldFocus} onBlur={handleFieldBlur} value={value} />

I've implemented this hook naively (source code) and the demo of the hook can be found at the bottom of my portfolio: https://brian-le.surge.sh.

I could rewrite this hook for better performance and readability and open a PR if required :)

Edit: validateFieldAfterSubmit is used to validate each field one by one after submit, for example:

const handleSubmit = useCallback(
    async e => {
      e.preventDefault();

      if (!validateFieldAfterSubmit('email')) return;

      try {
        // async code here
      } catch (err) {
        setFormMsg('Oops, something went wrong');

        return;
      }

      setFormMsg('Success!');
    },
    [validateFieldAfterSubmit],
  );