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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@unobtrusive/react-form-builder

v0.1.0-alpha.2

Published

A form builder for React, inspired by Rails.

Downloads

3

Readme

React Form Builder

Several server side frameworks are designed to handle form data if the inputs are named in a certain way. For e.g., in Rails, if you have an input named user[name], and is submitted as part of a form, then the server would parse and convert it to a Hash as such: { "user" => { "name" => <value> } }. This allows the server to access the input value as: params["user"]["name"], where params is the variable through which the parsed Hash is accessible.

Don't worry if you don't understand Rails; the point is that the convention of square brackets inside an input's name allows the server to store the value in a nice data structure which is then easier to work with. The only problem is that it can get cumbersome to write such form with inputs having these square brackets in their name, especially if it's a very complex form:

<form action="/users" method="post">
  <input type="text" name="user[name][first]" />
  <input type="text" name="user[name][last]" />

  <input type="text" name="user[address][first_line][house_no]" />
  <input type="text" name="user[address][first_line][street]" />

  <input type="text" name="user[address][second_line][state]" />
  <input type="text" name="user[address][second_line][country]" />

  <input type="text" name="user[hobbies][]" />
  <input type="text" name="user[hobbies][]" />
  <input type="text" name="user[hobbies][]" />
</form>

Wouldn't it be nice to not care about whether or not you've properly put those square brackets and everything is working properly because of it?

Using the @unobtrusive/react-form-builder package, the above form can be re-written like such:

<builder.form action="/users" method="post" resource="user">
  <builder.fields name="name">
    <builder.input type="text" name="first" />
    <builder.input type="text" name="last" />
  </builder.fields>

  <builder.fields name="address">
    <builder.fields name="first_line">
      <builder.input type="text" name="house_no" />
      <builder.input type="text" name="street" />
    </builder.fields>
  </builder.fields>

  <builder.fields name="address">
    <builder.fields name="second_line">
      <builder.input type="text" name="state" />
      <builder.input type="text" name="country" />
    </builder.fields>
  </builder.fields>

  <builder.input type="text" name="hobbies" collection />
  <builder.input type="text" name="hobbies" collection />
  <builder.input type="text" name="hobbies" collection />
</builder.form>

Now the form goes from a nice flat structure to a nested one, but those extra lines of code will make sure that the form, when submitted, will be compatible with the server. Also, it's a lot better to read as it follows a visual grouping of what inputs belong together.

Installation

Via NPM

$ npm install @unobtrusive/react-form-builder

Or via Yarn

$ yarn add @unobtrusive/react-form-builder

Features

There are 5 components provided by this package:

  • form
  • fields
  • input
  • select
  • textarea

all namespaced inside the builder object. So the usage would look like:

import builder from "@unobtrusive/react-form-builder";

<builder.form {/* ... */}>
  <builder.input {/* ... */} />
  {/* ... */}
</builder.form>

All the components mentioned above are just thin wrappers on the original components. You just need to pass some special props to these in order for them to work. The rest of the props (including the children) are just forwarded unchanged to the original component.

Also note that even the ref passed to these components will be forwarded to the original components, which is also the reason why these are very thin wrappers.

form component

Custom props

  • resource: Will be used to determine the first part of the input names

Usage

<builder.form action="/users" method="post" resource="user">
  {/* ... */}
</builder.form>

/* == (is equivalent to) */

<form action="/users" method="post">
  {/* ... */}
</form>

Except for resource, every other prop is forwarded to the original component. The resource prop is internally used by the component to set up things which will then be used by the other components in the list.

input component

Custom props

  • name: Will be used to determine what goes inside the square brackets. Should be used in conjunction with form
  • collection: Will be used to determine if the input is part of a collection or not. If it is, then empty square brackets are appended at the end

Usage

<builder.form action="/users" method="post" resource="user">
  <builder.input type="email" name="email" />
</builder.form>

/* == (is equivalent to) */

<form action="/users" method="post">
  <input type="email" name="user[email]" />
</form>

If inputs are part of a collection:

<builder.form action="/users" method="post" resource="user">
  <builder.input type="checkbox" name="superpowers" collection />
  <builder.input type="checkbox" name="superpowers" collection />
  <builder.input type="checkbox" name="superpowers" collection />
  <builder.input type="checkbox" name="superpowers" collection />
</builder.form>

/* == (is equivalent to) */

<form action="/users" method="post">
  <input type="checkbox" name="user[superpowers][]" />
  <input type="checkbox" name="user[superpowers][]" />
  <input type="checkbox" name="user[superpowers][]" />
  <input type="checkbox" name="user[superpowers][]" />
</form>

fields component

Doesn't render any component. It is used to create a nested structure for more complex form.

Custom props

  • name: Same as input. Except, it is used when names become complex and they have multiple parts to them
  • resource: Same as form. Except, should only be used when the parent form is not rendered by the builder. For e.g., when the form is rendered from somewhere else and you only want to render a part of it using the builder
  • collection: Same as input. Except, it would act on the inputs passed to it as children instead of acting on the component itself (also because the fields component doesn't render anything)

Usage

<builder.form action="/users" method="post" resource="user">
  <builder.fields name="address">
    <builder.input type="number" name="latitude" />
    <builder.input type="number" name="longitude" />
  </builder.fields>
</builder.form>

/* == (is equivalent to) */

<form action="/users" method="post">
  <input type="number" name="user[address][latitude]" />
  <input type="number" name="user[address][longitude]" />
</form>

Can also be used alongside simple named inputs:

<builder.form action="/users" method="post" resource="user">
  <builder.input type="email" name="email" />

  <builder.fields name="address">
    <builder.input type="number" name="latitude" />
    <builder.input type="number" name="longitude" />
  </builder.fields>
</builder.form>

/* == (is equivalent to) */

<form action="/users" method="post">
  <input type="email" name="user[email]" />

  <input type="number" name="user[address][latitude]" />
  <input type="number" name="user[address][longitude]" />
</form>

Any level of nesting is possible:

<builder.form action="/users" method="post" resource="user">
  <builder.fields name="address">
    <builder.fields name="coordinates">
      <builder.fields name="geometric">
        <builder.input type="number" name="latitude" />
        <builder.input type="number" name="longitude" />
      </builder.fields>
    </builder.fields>
  </builder.fields>
</builder.form>

/* == (is equivalent to) */

<form action="/users" method="post" resource="user">
  <input type="number" name="user[address][coordinates][geometric][latitude]" />
  <input type="number" name="user[address][coordinates][geometric][longitude]" />
</form>

When the main form is not rendered using the builder:

<form action="/users" method="post">
  {/* ... */}

  <builder.fields name="address" resource="user">
    <builder.input type="number" name="latitude" />
    <builder.input type="number" name="longitude" />
  </builder.fields>
</form>

/* == (is equivalent to) */

<form action="/users" method="post">
  {/* ... */}

  <input type="number" name="user[address][latitude]" />
  <input type="number" name="user[address][longitude]" />
</form>

If you don't pass the resource prop in the above case, then it will throw an error!

For grouped collection:

<builder.form action="/users" method="post" resource="user">
  <builder.fields name="addresses" collection>
    <builder.input type="number" name="latitude" />
    <builder.input type="number" name="longitude" />

    <builder.input type="number" name="latitude" />
    <builder.input type="number" name="longitude" />

    <builder.input type="number" name="latitude" />
    <builder.input type="number" name="longitude" />

    {/* ... */}
  </builder.fields>
</builder.form>

/* == (is equivalent to) */

<form action="/users" method="post">
  <input type="number" name="user[addresses][][latitude]" />
  <input type="number" name="user[addresses][][longitude]" />

  <input type="number" name="user[addresses][][latitude]" />
  <input type="number" name="user[addresses][][longitude]" />

  <input type="number" name="user[addresses][][latitude]" />
  <input type="number" name="user[addresses][][longitude]" />

  {/* ... */}
</form>

textarea component

Same as input.

select component

Same as input. For rendering option, you can use the usual component.