npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-simple-formkit

v2.0.4

Published

Support form handling simply. Configuration is simple, fast, and efficient.

Downloads

198

Readme

React Simple FormKit

Supports managing forms as uncontrolled. State updates only when watched. Simple and quick to configure with outstanding efficiency.

Table of Contents

Peer dependencies

"peerDependencies": {
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
  "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},

Features

  • Lightweight: only ~15KB
  • Easy setup: simple and quick configuration
  • Optimized for uncontrolled forms: no unnecessary re-renders
  • Comprehensive form management:
    • Values: get/set values, defaults, reset, ...
    • States: dirty, touched, custom states, ...
    • Errors: form-level and field-level errors, ...

Quick start

const { control } = useForm();

const handleSubmit = (data) => {
  alert(JSON.stringify(data));
};

return (
  <Form control={control} onSubmit={handleSubmit} onChange={console.log}>
    <button type="submit">Submit</button>
    <input required name="email" placeholder="email" />
    <input required name="password" type="password" placeholder="password" />
    {Array.from({ length: 10 }).map((_, index) => (
      <input name={`input${index + 1}`} placeholder={`input ${index + 1}`} />
    ))}
  </Form>
);

Core concepts

Modes of input field

Uncontrolled:

Basic HTML form elements whose value is natively managed by the browser. Such as
<input type="text">, <input type="checkbox">, <select>, or <textarea>.

Note: Or also UI library components that are simple wrappers around these native elements that still follow the browser’s standard behavior

Controlled:

Advanced UI components with complex INPUT AND OUTPUT . For example, a multiple select component expects an array as its input/output, but your form state might need to store the value as a string.

By using Controller, you can transform the value in both directions:

  • On input: split the stored string into an array to pass to the UI component.
  • On change: join the selected array back into a string before updating the form state.

Note: Or whenever you want to control the value of a field (e.g. by calling actions.setValue), you must wrap that field with a Controller. Without Controller, the field will not respond to external value changes.

Example:

<Controller
  name="multipleSelect"
  render={({ value = "", onChange, name }) => {
    return (
      <Select
        multiple
        name={name}
        value={value.split(",")}
        onChange={(e) => {
          const value = e.target.value;
          // value as array by default but on autofill value as string
          onChange(typeof value === "string" ? value : value.join(","));
        }}
      >
        <MenuItem value="10">Ten</MenuItem>
        <MenuItem value="20">Twenty</MenuItem>
        <MenuItem value="30">Thirty</MenuItem>
      </Select>
    );
  }}
/>

Captured

Advanced UI components with complex OUTPUT ONLY

In these cases, the field is captured:

  • You only listen to its value changes through a custom onChange.
  • You can sync the final value into the form when needed (via actions.setValue).

Example:

<LocalizationProvider dateAdapter={AdapterDayjs}>
  <DatePicker name="date" onChange={(newValue) => actions.setValue("date", newValue.format("MM/DD/YYYY"))} />
</LocalizationProvider>
const { control, actions } = useForm();

return (
  <Form control={control} onChange={console.log}>
    {/* Uncontrolled */}
    <input name="number1" placeholder="Enter a number" />
    {/* Controlled by Controller. And the value at this field can be controlled by actions.setValue() */}
    <Controller
      name="multipleSelect"
      render={({ value = "", onChange, name }) => {
        return (
          <Select
            multiple
            name={name}
            value={value.split(",")}
            onChange={(e) => {
              const value = e.target.value;
              // value as array by default but on autofill value as string
              onChange(typeof value === "string" ? value : value.join(","));
            }}
          >
            <MenuItem value="10">Ten</MenuItem>
            <MenuItem value="20">Twenty</MenuItem>
            <MenuItem value="30">Thirty</MenuItem>
          </Select>
        );
      }}
    />
    {/* Captured: This datepicker component changes value immediately with a click with custom onChange */}
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <DatePicker name="date" onChange={(newValue) => actions.setValue("date", newValue.format("MM/DD/YYYY"))} />
    </LocalizationProvider>
    <button type="button" onClick={() => alert(JSON.stringify(actions.getValues()))}>
      Get value
    </button>
    <button type="submit">Submit</button>
  </Form>
)

Note*: All fields in the form must follow one of the three modes above to ensure the form works correctly

Watching for updates

State updates only when observed via watch() or useWatch(), or by tracking changes through the Form component's onChange callback.

  • watch() will trigger a re-render at the form level.
  • useWatch() will trigger a re-render only in the component where it is called.
  • onChange is just a handler callback that is called when field values change.

Manage values

Get value

// watch
const fieldName = watch("fieldName")
const fieldName2 = watch("fieldName2")
// Or
const { fieldName2, fieldName } = watch(["fieldName", "fieldName2"])
// Or
const values = watch("values")

// useWatch
useWatch({ name: "fieldName" })
useWatch({ name: ["fieldName", "fieldName2"] })
useWatch({ compute: (values) => values.number > 10 ? true : false })

// actions.getValues()
const { actions } = useForm()
console.log(actions.getValues())

Set value

actions.setValue() can help to control value of a field. But that field must be controlled by Controller.

const { control, actions, watch } = useForm()
const number = watch("number")

return (
  <Form control={control} onChange={console.log}>
    <Controller
      name="number"
      render={({ name, value, onChange }) => (
        <input name={name} value={value} onChange={(e) => onChange(e.target.value)} />
      )}
    />
    <button type="button" onClick={() => actions.setValue("number", Number(number || 0) + 1 )}>
      Increase
    </button>
    <button type="submit" disabled={!isDirty}>
      Submit
    </button>
  </Form>
)

Default values and reset

const dummyFields = Array.from({ length: 10 }).reduce(
  (acc, _, index) => ({ ...acc, [`input${index + 1}`]: `input${index + 1}` }),
  {}
);

const { control, watch, actions } = useForm({ defaultValues: dummyFields });

const handleSubmit = async (newValues) => {
  // update to server
  await new Promise(res => setTimeout(res, 1000))
  // reset with new defaultValues
  actions.reset(newValues)
}

return (
  <Form control={control} onSubmit={handleSubmit} onChange={console.log}>
    {Object.keys(dummyFields).map((name) => (
      <input name={name} placeholder={name} />
    ))}
    <button type="reset" disabled={!isDirty}>
      Reset
    </button>
    <button type="submit" disabled={!isDirty}>
      Submit
    </button>
  </Form>
);

Manage states

Get field states

// watch() or useWatch()
const isFormDirty = watch("isDirty")
const { fieldName: {...}, fieldName2: {...} } = watch("fieldStates")
const { isDirty, isTouched } = watch("fieldStates.fieldName")
const isFieldDirty = watch("fieldStates.fieldName.isDirty")
const fieldCustomState = watch("fieldStates.fieldName.customState")

Set field states

actions.setFieldStateProperty('fieldName', 'customState', { hello: "world" })
// This will trigger re-render at watch(), useWatch() and render function in Controller

Manage errors

Get field error

// watch() or useWatch()
const isFormError = watch("isError")
const fieldError = watch("errors.fieldName")
const { fieldName, fieldName2 } = watch("errors")

Set field error

// actions.setError()
const { control, actions, watch } = useForm()
const { isError, "errors.input1": input1Error  } = watch(["isError", "errors.input1"])

return (
  <Form control={control} onChange={console.log}>
    <input name="input1" />
    {input1Error && <span className="error-message">{input1Error}</span>}
    <button type="button" onClick={() => actions.setError("number", "This is error message")}>
      Set Error
    </button>
    <button type="submit" disabled={!isDirty}>
      Submit
    </button>
  </Form>
)

APIs

useForm

Generic props:

  • defaultValues: Object Example
  • shouldUnRegister: Boolean Default is false,
  • shouldConvertNumber: Boolean Default is false
  • numberFields: Array if passed, shouldConvertNumber will set true. Lib auto load field with type number by default

Return:

  • control: contains methods and utilities to control the form.
  • watch(name): Function Example
  • actions is an object that contains utilities
    • actions.reset(): Example
    • actions.getValues(): Example
    • actions.setValue(): Example
    • actions.setError(): Example
    • actions.clearError()
    • actions.clearErrors()
    • actions.getNumberFields()
    • actions.getDefaultValues()
    • actions.setFieldStateProperty(): Example

Form

Generic props:

  • control: received from useForm()
  • onSubmit: (currentValues) => {} called when the form is submitted via a button with type='submit'
  • onChange: (name, value, currentValues) => {} called when any field value changes

Controller

Generic props:

  • name
  • defaultValue
  • render({name, value, onChange, customState, setCustomState})

useController

Generic props:

  • name
  • defaultValue

Return: everything in render function above

useWatch

Example

Generic props:

  • name: String | Array
  • compute: Function that will calculate from form values and return a value. It will make re-render when the result changes

useFormContext

Return:

  • watch
  • actions

Examples

  • https://codesandbox.io/p/sandbox/react-simple-formkit-examples-rhmhjj

Contact

For any ideas or issues, please get in touch with me at [email protected]