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

@carlosot2/react-form-control

v1.0.4

Published

React form controller with URL query synchronization.

Readme

⚡ React Form Control

A form controller for React with TypeScript support and URL query synchronization.

Manage form state, input changes, submissions, debouncing and query parameters with a straightforward API.

npm version npm downloads GitHub license TypeScript


✨ Features

  • 🎛️ Simple form state management
  • ⌨️ Automatic input change handling
  • ☑️ Built-in checkbox support with multiple values
  • 📤 Form submission controller
  • ⏱️ Optional debounced submission with submitOnChange
  • 🔗 URL query parameter synchronization
  • ↩️ Browser back/forward synchronization
  • 🧩 Generic TypeScript form types

📦 Installation

npm install @carlosot2/react-form-control

🚀 Basic Usage

Define the structure of your form:

type SearchForm = {
    name: string
    author: string
}

Create the form controller:

import { useFormController } from '@carlosot2/react-form-control'

function Search() {
    const { InputsController, SubmitController } =
        useFormController<SearchForm>({
            handleSubmit
        })

    async function handleSubmit(data: SearchForm) {
        console.log(data)
    }

    return (
        <form onSubmit={SubmitController.onSubmit}>
            <input
                type="text"
                name="name"
                value={InputsController.data.name ?? ''}
                onChange={InputsController.onChange}
            />

            <input
                type="text"
                name="author"
                value={InputsController.data.author ?? ''}
                onChange={InputsController.onChange}
            />

            <button type="submit">
                Search
            </button>
        </form>
    )
}

The controller stores values using each input's name property.

For example:

InputsController.data

could contain:

{
    name: 'Dandadan',
    author: 'Yukinobu Tatsu'
}

🎛️ InputsController

InputsController is responsible for reading and updating form values.

const { InputsController } = useFormController(...)

It provides:

InputsController.data
InputsController.onChange
InputsController.changeValue

data

Contains the current form values.

InputsController.data.name
InputsController.data.author

onChange

Pass it directly to supported inputs:

<input
    name="name"
    value={InputsController.data.name ?? ''}
    onChange={InputsController.onChange}
/>

The input name is used as the key inside the form data.

changeValue

Updates a value manually without requiring an input event.

InputsController.changeValue('name', 'Dandadan')

This is useful for:

  • custom components
  • clearing fields
  • buttons
  • manually controlled values
  • external state changes

Example:

<button
    type="button"
    onClick={() => InputsController.changeValue('name', '')}
>
    Clear
</button>

☑️ Checkbox Support

React Form Control automatically handles checkbox values as arrays.

Use the same name for checkboxes that belong to the same field:

<input
    type="checkbox"
    name="genres"
    value="action"
    checked={InputsController.data.genres?.includes('action') ?? false}
    onChange={InputsController.onChange}
/>

<input
    type="checkbox"
    name="genres"
    value="comedy"
    checked={InputsController.data.genres?.includes('comedy') ?? false}
    onChange={InputsController.onChange}
/>

When both options are selected, the form data will contain:

{
    genres: ['action', 'comedy']
}

Selecting an unchecked option adds its value to the array, while selecting an already checked option removes it.

Checkbox values also work with Query Control, where repeated values are represented as repeated URL query parameters:

?genres=action&genres=comedy

📤 SubmitController

SubmitController provides the form submission handler.

<form onSubmit={SubmitController.onSubmit}>

When the form is submitted, the current form data is passed to handleSubmit.

const { InputsController, SubmitController } =
    useFormController<SearchForm>({
        handleSubmit
    })

async function handleSubmit(data: SearchForm) {
    console.log(data)
}

⏱️ Automatic Submission

Enable submitOnChange to automatically submit the form shortly after its values change.

const { InputsController } =
    useFormController<SearchForm>({
        handleSubmit,
        submitOnChange: true
    })

Example:

async function handleSubmit(data: SearchForm) {
    const response = await searchTitles(data)
    console.log(response)
}

This is useful for:

  • search bars
  • filters
  • live search
  • dynamic results
  • auto-updating forms

Changes are debounced to avoid submitting repeatedly while the user is still typing.


🔗 Query Control

React Form Control can synchronize form values with the URL query string.

Enable it with:

const { InputsController, SubmitController } =
    useFormController<SearchForm>({
        queryControl: true,
        handleQueryChange
    })

Then define what should happen whenever the query changes:

async function handleQueryChange(query: string) {
    console.log(query)
}

A form like:

{
    name: 'Dandadan',
    genres: ['action', 'comedy']
}

can produce:

?name=Dandadan&genres=action&genres=comedy

🔄 Query Synchronization

With queryControl enabled, the controller keeps the form synchronized with the URL.

Form Data
    ↓
URL Query
    ↓
Browser History
    ↓
Form Data

This includes browser navigation such as:

  • Back
  • Forward

For example:

/titles?name=naruto

then:

/titles?name=bleach

If the user presses Back, the form values are synchronized with:

?name=naruto

🔎 Query Example

type Filters = {
    name: string
    genresIds: string[]
}

function SearchPage() {
    const { InputsController, SubmitController } =
        useFormController<Filters>({
            queryControl: true,
            handleQueryChange
        })

    async function handleQueryChange(query: string) {
        const response = await fetch(`/api/titles?${query}`)
        const data = await response.json()

        console.log(data)
    }

    return (
        <form onSubmit={SubmitController.onSubmit}>
            <input
                name="name"
                value={InputsController.data.name ?? ''}
                onChange={InputsController.onChange}
            />

            <input
                type="checkbox"
                name="genresIds"
                value="1"
                checked={
                    InputsController.data.genresIds?.includes('1') ?? false
                }
                onChange={InputsController.onChange}
            />

            <input
                type="checkbox"
                name="genresIds"
                value="2"
                checked={
                    InputsController.data.genresIds?.includes('2') ?? false
                }
                onChange={InputsController.onChange}
            />

            <button type="submit">
                Search
            </button>
        </form>
    )
}

🧩 TypeScript

React Form Control supports generic form types.

type LoginForm = {
    email: string
    password: string
}

Pass the type to the hook:

const { InputsController } =
    useFormController<LoginForm>({
        handleSubmit
    })

Now:

InputsController.data.email

is typed as:

string

and:

InputsController.data.password

is also typed as:

string

This allows the controller to stay generic while preserving the structure of each form.


⚙️ Configuration

Standard form

useFormController({
    handleSubmit
})

Automatic submit

useFormController({
    handleSubmit,
    submitOnChange: true
})

Query-controlled form

useFormController({
    queryControl: true,
    handleQueryChange
})

Query-controlled form with automatic updates

useFormController({
    queryControl: true,
    handleQueryChange,
    submitOnChange: true
})

📚 API

useFormController<TData>()

useFormController<TData>(config)

Returns:

{
    InputsController,
    SubmitController
}

InputsController

{
    data,
    onChange,
    changeValue
}

SubmitController

{
    onSubmit
}

📄 License

Distributed under the MIT License.