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 🙏

© 2026 – Pkg Stats / Ryan Hefner

formmeld

v0.1.2

Published

Uncontrolled form ownership for React and React Native-compatible applications

Readme

Formmeld

Formmeld gives one object ownership of form values while inputs remain uncontrolled. It works with React DOM and React Native-compatible input adapters, without putting DOM or React Native APIs in its value core.

Install

npm install formmeld

React 18 or 19 must be installed by the consuming application.

Ownership model

FormInputs is the source of truth for collected and programmatically assigned values. A mounted field registers an imperative applyValue adapter:

  • If the form already owns the name, registration applies that value to the field immediately unless it is Object.is equal to the field's initialValue. This avoids rewriting an uncontrolled field during React StrictMode's setup/cleanup/setup replay.
  • Otherwise, initialValue initializes form ownership without applying it back to the registering field.
  • form.setValue(name, value) stores the value and applies it to every mounted registration. Programmatically assigned values remain owned across field unmounts and are applied if a field later registers that name.
  • A registration's setValue(value) represents user input. It updates the form and peer registrations while skipping its own applier.
  • unregister() is idempotent. Writes through an unregistered handle do nothing. Unregistering one of several same-name fields preserves the shared value. Unregistering the final field removes a value last written by a field, but preserves a value last written through form.setValue.
  • unsetValue(name) removes ownership and applies undefined to mounted fields.

Multiple mounted fields with the same name are supported and synchronized in registration order.

API

import {
  Form,
  FormContext,
  FormInputs,
  useFieldRegistration,
  useForm,
  useOptionalFieldRegistration,
  useOptionalForm
} from "formmeld"

FormInputs

  • getValue(name) returns the currently owned value.
  • setValue(name, value) stores and programmatically applies a value.
  • setValueWithHidden(name, value) uses the same storage and propagation path, then returns React.createElement("input", {name, type: "hidden", value}). Nullish values become "" in the element. This explicit compatibility helper is universal: it does not inspect the platform, mount the element, or require React DOM or React Native.
  • unsetValue(name) removes and programmatically unsets a value.
  • asObject() converts bracketed names into an object using form-data-objectizer semantics. For example, project[contributors][0][name] becomes nested objects with a "0" key. Flat fields named __proto__, constructor, or prototype are emitted as safe own properties. Those names inside bracket paths are rejected by form-data-objectizer to prevent prototype mutation.
  • submit() calls the current onSubmit callback and returns its result.
  • registerField(name, {applyValue, initialValue}) returns {setValue, unregister}.

React API

Passing a directly-owned instance to <Form form={form}> is the preferred modern path. useForm() reads it, and throws when used outside Form. useFieldRegistration() handles registration cleanup and returns a stable {setValue} handle. Registration uses a layout effect, so a later layout effect in the same component can write through the handle on mount and after a name/form replacement. Layout effects do not run during server rendering, and registration does not require DOM APIs. For compatibility, omitting form makes Form create and retain one local FormInputs instance for its mounted lifetime.

Reusable inputs that may render outside a form can use useOptionalForm(), which returns the current FormInputs or null. They can register through useOptionalFieldRegistration(name, options), where name may be a string, null, or undefined. The returned {setValue} handle remains stable and its write is a safe no-op until both a form and a non-empty string name are present. It automatically activates, unregisters, and retargets as the surrounding form or name changes. Use the strict hooks when form membership and a valid name are required.

Form also supports the migration props from API Maker:

  • formObjectRef is assigned the selected FormInputs after commit and reset to null during cleanup.
  • setForm(form) runs after commit.
  • formRef is forwarded when an HTML form is rendered.
  • htmlFormProps is spread onto that HTML form.
  • onSubmit becomes the selected FormInputs submit callback.

Set useHtmlForm={true} to render a web <form>. Its submit event is prevented and delegated to form.submit(). useHtmlForm defaults strictly to false; otherwise Form renders only its context provider, which is suitable for React Native-compatible trees. Calling setValueWithHidden does not change this rendering behavior.

Form and FormProps are generic over the optional rendered form element. React DOM consumers can use Form<HTMLFormElement>-compatible refs and HTML form props, while the default element type is unknown so declarations remain importable in projects without the DOM library.

API Maker does not provide a compatibility re-export. Existing consumers must add formmeld and import the form API from this package directly.

React Native TextInput adapter

The adapter remains uncontrolled: defaultValue initializes the native control, while Formmeld owns collected values.

import {useRef} from "react"
import {TextInput} from "react-native"
import {useOptionalFieldRegistration} from "formmeld"

function NameInput({defaultValue = "", name}) {
  const inputRef = useRef(null)
  const field = useOptionalFieldRegistration(name, {
    initialValue: defaultValue,
    applyValue(value) {
      inputRef.current?.setNativeProps({
        text: value == null ? "" : String(value)
      })
    }
  })

  return (
    <TextInput
      defaultValue={defaultValue}
      onChangeText={field.setValue}
      ref={inputRef}
    />
  )
}

Programmatic changes such as form.setValue("profile[name]", "Kasper") update the mounted native control. Typing uses the registration handle, so the originating control is not written back to and its cursor is not reset.

Web input adapter

import {useRef} from "react"
import {useOptionalFieldRegistration} from "formmeld"

function EmailInput({defaultValue = "", name}) {
  const inputRef = useRef(null)
  const field = useOptionalFieldRegistration(name, {
    initialValue: defaultValue,
    applyValue(value) {
      if (inputRef.current) {
        inputRef.current.value = value == null ? "" : String(value)
      }
    }
  })

  return (
    <input
      defaultValue={defaultValue}
      name={name}
      onInput={(event) => field.setValue(event.currentTarget.value)}
      ref={inputRef}
    />
  )
}

Create and own the form instance above the component:

const form = useMemo(() => new FormInputs(), [])

return (
  <Form form={form} onSubmit={() => save(form.asObject())} useHtmlForm>
    <EmailInput name="account[email]" />
    <button type="submit">Save</button>
  </Form>
)

License

MIT