A lightweight framework and utility for building powerful forms in React applications
See the codeSay hello to the best React form library you have ever used! Informed is an extensive, simple, and efficient solution for creating basic to complex forms in React. Out of the box you get the ability to grab and manipulate values, validate fields, create custom inputs, multi-step forms, array fields, and much much more!
Oh and YES WE USE HOOKS!
npm install --save informed
See for yourself.
By default it comes with native dom inputs that are controlled by informed.
import { Form, Input, Select, Checkbox, Relevant, Debug } from 'informed';
const onSubmit = ({ values }) => console.log(values);
const ExampleForm = () => (
<Form onSubmit={onSubmit}>
<Input name="name" label="Name" placeholder="Elon" />
<Input name="age" type="number" label="Age" required="Age Required" />
<Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
<Select name="car" label="Car" initialValue="ms">
<option value="ms">Model S</option>
<option value="m3">Model 3</option>
<option value="mx">Model X</option>
<option value="my">Model Y</option>
</Select>
<Checkbox name="married" label="Married?" />
<Relevant when={({ formState }) => formState.values.married}>
<Input name="spouse" label="Spouse" />
</Relevant>
<button type="submit">Submit</button>
<Debug />
</Form>
);
informed was designed to support many important features
[ 'a', 'b' ] or [ { name: 'Joe', age: 29 }, { name: 'Hope', age: 24 }]state.values.friends[1].brother.parents.cars[0].modelBut what if you dont want the out of the box stuff??
No problem, see example below!
import { useForm, useField, Relevant, FormState } from 'informed';
// Step 1. Build your form component ---------------------
const Form = ({ children, ...rest }) => {
const { formController, render, userProps } = useForm(rest);
return render(
<form noValidate {...userProps} onSubmit={formController.submitForm}>
{children}
</form>
);
};
// Step 2. Build your input components --------------------
const Input = props => {
const { render, informed, userProps, ref } = useField({
type: 'text',
...props
});
const { label, id, ...rest } = userProps;
return render(
<>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} {...informed} {...rest} />
</>
);
};
const Checkbox = props => {
const { render, informed, userProps, ref } = useField({
type: 'checkbox',
...props
});
const { label, id, ...rest } = userProps;
return render(
<>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} {...informed} {...rest} />
</>
);
};
const ErrorInput = props => {
const { render, informed, userProps, fieldState, ref } = useField({
type: 'text',
...props
});
const { label, id, ...rest } = userProps;
const { showError } = fieldState;
const style = showError ? { border: 'solid 1px red' } : null;
return render(
<>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} {...informed} {...rest} style={style} />
{showError && <small style={{ color: 'red' }}>{fieldState.error}</small>}
</>
);
};
const Select = props => {
const { render, informed, userProps, ref } = useField({
type: 'select',
...props
});
const { label, id, children, ...rest } = userProps;
return render(
<>
<label htmlFor={id}>{label}</label>
<select id={id} ref={ref} {...informed} {...rest}>
{children}
</select>
</>
);
};
// Step 3. Build your forms! ---------------------------
const onSubmit = ({ values }) => console.log(values);
const ExampleForm = () => (
<Form onSubmit={onSubmit}>
<Input name="name" label="Name" placeholder="Elon" />
<ErrorInput name="age" type="number" label="Age" required="Age Required" />
<Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
<Select name="car" label="Car" initialValue="ms">
<option value="ms">Model S</option>
<option value="m3">Model 3</option>
<option value="mx">Model X</option>
<option value="my">Model Y</option>
</Select>
<Checkbox name="married" label="Married?" />
<Relevant when={({ formState }) => formState.values.married}>
<Input name="spouse" label="Spouse" />
</Relevant>
<button type="submit">Submit</button>
<Debug />
</Form>
);
Informed took the following into consideration when being built:
informed was designed to be able to handle very complex forms at scale
informed was designed to be able to write complex forms with very little intuitive code
<Form onSubmit={onSubmit}>
<Input name="name" label="Name" placeholder="Elon" />
<Input name="age" type="number" label="Age" required="Age Required" />
<Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
<Checkbox name="married" label="Married?" />
<Relevant when={({ formState }) => formState.values.married}>
<Input name="spouse" label="Spouse" />
</Relevant>
<Debug />
</Form>
ZERO Dependency: informed was designed to rely on no other library
JSON Schema: informed was designed to support rendering forms based on pure JSON
Feature List: informed was designed to support many important features
[ 'a', 'b' ] or [ { name: 'Joe', age: 29 }, { name: 'Hope', age: 24 }]state.values.friends[1].brother.parents.cars[0].modelconst path = 'state.values.friends[1].brother.name';
This project cotains three important directories, src, vitedocs, and __tests__
Note: some things were left out as they are not super important or are going to be deprecated or removed in future.
project_root
│
├── index.d.ts # all of informeds types live here
│
├── src # all of informeds source code lives here ( except types index.d.ts )
│ ├── components # React Components
│ ├── hooks # Hooks
│ ├── Context.js # Internal Contexts used in this library
│ ├── debug.js # Basically the https://github.com/visionmedia/debug library but shrunk down
│ ├── fieldMap.js # Default field adapter, used when working with schema forms
│ ├── index.js # all external exports ( everything exposed to users )
│ ├── ObjectMap.js # internal data structure for manipulating the form state objects
│ ├── FormController.js # The brains behind the library, this controls basically everything :)
│ └── utils.js # Any utilities used throughout this project
│
├── vitedocs # All the informed docs build via vite ( instead of storybook which was old way )
│ ├── App.jsx # basic react app with react-router
│ ├── Header # top nav of the docs
│ ├── hooks # helper hooks for docs
│ ├── Nav # side nav of the docs
│ ├── Pages # main level pages of the app
│ │ ├──ApiReference # Self explanatory :)
│ │ ├──Examples # Examples of all sorts of usecases
│ │ ├──GettingStarted # Also Self explanatory :)
│ │ └──Playground.jsx # Uses Sandpack to allow users to test any of the examples
│ │
│ ├── SideBySide.jsx # helper component for showing code example and output side by side
│ ├── index.css # documentation styles
│ ├── prism.css # styles for code blocks
│ └── ... # other stuff
│
└── __tests__ # extensive unit testing
├── components # tests for informed components
├── hooks # tests for informed hooks
├── ObjectMap.test # tests for the internal data structure
├── Schema.test # tests for usage of JSON schema rendered forms
└── utils.test # tests for internal library utilities
FormControllerFormController is the brains of informed, it holds the state object and is responsible for:
ObjectMapObjectMap is the internal data structre that is responsible for managing the internal state object. Reads and writes all go through this data structure. Example:
// State object
const state = { values: {} };
// Set the states values first friends brothers age to 30
ObjectMap.set(state.values, 'friends[0].brothers.age', 30);
useFielduseField is the first class citizen of informed, it's responsible for registering a field by name to the FormController.
Though there is not explicitly a component called Context here the concept is KEY to understanding informed. Context alows us to register fields in a highly nested structure and allows us to do wild things such as scoping.
Below depicts the core to how this library works. Form controller is the master of all form elements and everyone else just subscribes to events that it triggers. The events can be caused by function calls from elsewhere. In the example below we depict what happens when a user types in an "H" in the name field
<Form>
<Input name="name" /> {/* --> useField("name") --> useFieldState("name") */}
<Debug /> {/* --> useFormState() */}
</Form>
+----------------+
| FormController | < ──────────────────────────────────────────
| state {} | │
+-------+--------+ │
│ │
│ event("field", "name") │
v │
+-------------------+ │
| Event System | ──────────────── │
+--------+----+-----+ │ │
│ │ │
│ event("field", "name") │ event("field", "name") │ setValue("H")
│ │ │
v v │
+-------------------+ +------------------------+ │
| useFormState() | | useFieldState("name") | │
+-------------------+ +------------------------+ │
^ ^ │
│ uses │ uses │
│ │ │
+-------------------+ +------------------------+ │
| <Debug /> | | useField("name") | ─────────
+-------------------+ +------------------------+
^
│ uses
│
+------------------------+
| <Input name="name" /> |
+------------------------+
I know, I know the types kinda suck. I personally don't use typescript so I have had to rely on other TS wizard devs to help maintain the types. When trying to type very complex objects such as FormState especially parts like FormState.values which can literally be an object that contains anything there are lots of opinions on how to properly type it. I'm actively seeking help here.
As stated earlier, docs now live in the vitedocs directory. Its called vitedocs because I migrated them from storybook to now use a custom singe page react app built with vite... pronounced "veet" by the way :)
Most of the docs contain examples where each example has a directory.
For example, if you look at the vitedocs/Pages/GettingStarted/CarColors you will find the CarColors.jsx file.
This file contains the page that gets shown ( whatever info you want to show on that page ) and then the example itself.
Because we use vite, we can take advantage of the fact that vite can import both a react component normally, and also the raw text from that file! Therefore creating examples is as simple as this!
Note how we make use of the SideBySide component to render our code and example
import Example from './Example';
import exampleCode from './Example.jsx?raw';
//... other stuff
<SideBySide
leftHeader={<h3>Example: </h3>}
rightHeader={<h3>Code:</h3>}
left={<Example />}
right={<Code links input1={exampleCode} />}
/>;
useConditional hook.The docs site exposes raw markdown for LLMs and other tools that prefer plain text over the interactive React UI.
llms.txt — an index of all documentation pages with links to their markdown URLs.md endpoints — append .md to any doc URL to get the raw markdown for that pageIn local dev the same URLs work under the Vite dev server:
No separate markdown files are maintained for doc pages. A Vite plugin (vite-plugins/llmsPlugin.js) generates everything at dev and build time:
vitedocs/llms/parseRoutes.js parses vitedocs/Routes/Routes.jsx to map each doc route to its React component source file.vitedocs/llms/extractMarkdown.js walks each page's JSX and produces markdown: headings, prose (Info blocks, lists, links), and example code from Vite ?raw imports. README.md and CHANGELOG.md are served as-is for their routes.llms.txt and .md URLs on demand.npm run build:docs writes static llms.txt and .md files into docs/ alongside the built SPA for GitHub Pages.Extraction is best-effort for JSX pages (interactive demos and some UI chrome are omitted), but it is enough for LLMs to consume the docs without duplicating content.
322 followers · starred Jan 2020
277 followers · starred Apr 2019
414 followers · starred Jun 2022
225 followers · starred Jun 2022
A lightweight framework and utility for building powerful forms in React applications
See the codeSay hello to the best React form library you have ever used! Informed is an extensive, simple, and efficient solution for creating basic to complex forms in React. Out of the box you get the ability to grab and manipulate values, validate fields, create custom inputs, multi-step forms, array fields, and much much more!
Oh and YES WE USE HOOKS!
npm install --save informed
See for yourself.
By default it comes with native dom inputs that are controlled by informed.
import { Form, Input, Select, Checkbox, Relevant, Debug } from 'informed';
const onSubmit = ({ values }) => console.log(values);
const ExampleForm = () => (
<Form onSubmit={onSubmit}>
<Input name="name" label="Name" placeholder="Elon" />
<Input name="age" type="number" label="Age" required="Age Required" />
<Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
<Select name="car" label="Car" initialValue="ms">
<option value="ms">Model S</option>
<option value="m3">Model 3</option>
<option value="mx">Model X</option>
<option value="my">Model Y</option>
</Select>
<Checkbox name="married" label="Married?" />
<Relevant when={({ formState }) => formState.values.married}>
<Input name="spouse" label="Spouse" />
</Relevant>
<button type="submit">Submit</button>
<Debug />
</Form>
);
informed was designed to support many important features
[ 'a', 'b' ] or [ { name: 'Joe', age: 29 }, { name: 'Hope', age: 24 }]state.values.friends[1].brother.parents.cars[0].modelBut what if you dont want the out of the box stuff??
No problem, see example below!
import { useForm, useField, Relevant, FormState } from 'informed';
// Step 1. Build your form component ---------------------
const Form = ({ children, ...rest }) => {
const { formController, render, userProps } = useForm(rest);
return render(
<form noValidate {...userProps} onSubmit={formController.submitForm}>
{children}
</form>
);
};
// Step 2. Build your input components --------------------
const Input = props => {
const { render, informed, userProps, ref } = useField({
type: 'text',
...props
});
const { label, id, ...rest } = userProps;
return render(
<>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} {...informed} {...rest} />
</>
);
};
const Checkbox = props => {
const { render, informed, userProps, ref } = useField({
type: 'checkbox',
...props
});
const { label, id, ...rest } = userProps;
return render(
<>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} {...informed} {...rest} />
</>
);
};
const ErrorInput = props => {
const { render, informed, userProps, fieldState, ref } = useField({
type: 'text',
...props
});
const { label, id, ...rest } = userProps;
const { showError } = fieldState;
const style = showError ? { border: 'solid 1px red' } : null;
return render(
<>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} {...informed} {...rest} style={style} />
{showError && <small style={{ color: 'red' }}>{fieldState.error}</small>}
</>
);
};
const Select = props => {
const { render, informed, userProps, ref } = useField({
type: 'select',
...props
});
const { label, id, children, ...rest } = userProps;
return render(
<>
<label htmlFor={id}>{label}</label>
<select id={id} ref={ref} {...informed} {...rest}>
{children}
</select>
</>
);
};
// Step 3. Build your forms! ---------------------------
const onSubmit = ({ values }) => console.log(values);
const ExampleForm = () => (
<Form onSubmit={onSubmit}>
<Input name="name" label="Name" placeholder="Elon" />
<ErrorInput name="age" type="number" label="Age" required="Age Required" />
<Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
<Select name="car" label="Car" initialValue="ms">
<option value="ms">Model S</option>
<option value="m3">Model 3</option>
<option value="mx">Model X</option>
<option value="my">Model Y</option>
</Select>
<Checkbox name="married" label="Married?" />
<Relevant when={({ formState }) => formState.values.married}>
<Input name="spouse" label="Spouse" />
</Relevant>
<button type="submit">Submit</button>
<Debug />
</Form>
);
Informed took the following into consideration when being built:
informed was designed to be able to handle very complex forms at scale
informed was designed to be able to write complex forms with very little intuitive code
<Form onSubmit={onSubmit}>
<Input name="name" label="Name" placeholder="Elon" />
<Input name="age" type="number" label="Age" required="Age Required" />
<Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
<Checkbox name="married" label="Married?" />
<Relevant when={({ formState }) => formState.values.married}>
<Input name="spouse" label="Spouse" />
</Relevant>
<Debug />
</Form>
ZERO Dependency: informed was designed to rely on no other library
JSON Schema: informed was designed to support rendering forms based on pure JSON
Feature List: informed was designed to support many important features
[ 'a', 'b' ] or [ { name: 'Joe', age: 29 }, { name: 'Hope', age: 24 }]state.values.friends[1].brother.parents.cars[0].modelconst path = 'state.values.friends[1].brother.name';
This project cotains three important directories, src, vitedocs, and __tests__
Note: some things were left out as they are not super important or are going to be deprecated or removed in future.
project_root
│
├── index.d.ts # all of informeds types live here
│
├── src # all of informeds source code lives here ( except types index.d.ts )
│ ├── components # React Components
│ ├── hooks # Hooks
│ ├── Context.js # Internal Contexts used in this library
│ ├── debug.js # Basically the https://github.com/visionmedia/debug library but shrunk down
│ ├── fieldMap.js # Default field adapter, used when working with schema forms
│ ├── index.js # all external exports ( everything exposed to users )
│ ├── ObjectMap.js # internal data structure for manipulating the form state objects
│ ├── FormController.js # The brains behind the library, this controls basically everything :)
│ └── utils.js # Any utilities used throughout this project
│
├── vitedocs # All the informed docs build via vite ( instead of storybook which was old way )
│ ├── App.jsx # basic react app with react-router
│ ├── Header # top nav of the docs
│ ├── hooks # helper hooks for docs
│ ├── Nav # side nav of the docs
│ ├── Pages # main level pages of the app
│ │ ├──ApiReference # Self explanatory :)
│ │ ├──Examples # Examples of all sorts of usecases
│ │ ├──GettingStarted # Also Self explanatory :)
│ │ └──Playground.jsx # Uses Sandpack to allow users to test any of the examples
│ │
│ ├── SideBySide.jsx # helper component for showing code example and output side by side
│ ├── index.css # documentation styles
│ ├── prism.css # styles for code blocks
│ └── ... # other stuff
│
└── __tests__ # extensive unit testing
├── components # tests for informed components
├── hooks # tests for informed hooks
├── ObjectMap.test # tests for the internal data structure
├── Schema.test # tests for usage of JSON schema rendered forms
└── utils.test # tests for internal library utilities
FormControllerFormController is the brains of informed, it holds the state object and is responsible for:
ObjectMapObjectMap is the internal data structre that is responsible for managing the internal state object. Reads and writes all go through this data structure. Example:
// State object
const state = { values: {} };
// Set the states values first friends brothers age to 30
ObjectMap.set(state.values, 'friends[0].brothers.age', 30);
useFielduseField is the first class citizen of informed, it's responsible for registering a field by name to the FormController.
Though there is not explicitly a component called Context here the concept is KEY to understanding informed. Context alows us to register fields in a highly nested structure and allows us to do wild things such as scoping.
Below depicts the core to how this library works. Form controller is the master of all form elements and everyone else just subscribes to events that it triggers. The events can be caused by function calls from elsewhere. In the example below we depict what happens when a user types in an "H" in the name field
<Form>
<Input name="name" /> {/* --> useField("name") --> useFieldState("name") */}
<Debug /> {/* --> useFormState() */}
</Form>
+----------------+
| FormController | < ──────────────────────────────────────────
| state {} | │
+-------+--------+ │
│ │
│ event("field", "name") │
v │
+-------------------+ │
| Event System | ──────────────── │
+--------+----+-----+ │ │
│ │ │
│ event("field", "name") │ event("field", "name") │ setValue("H")
│ │ │
v v │
+-------------------+ +------------------------+ │
| useFormState() | | useFieldState("name") | │
+-------------------+ +------------------------+ │
^ ^ │
│ uses │ uses │
│ │ │
+-------------------+ +------------------------+ │
| <Debug /> | | useField("name") | ─────────
+-------------------+ +------------------------+
^
│ uses
│
+------------------------+
| <Input name="name" /> |
+------------------------+
I know, I know the types kinda suck. I personally don't use typescript so I have had to rely on other TS wizard devs to help maintain the types. When trying to type very complex objects such as FormState especially parts like FormState.values which can literally be an object that contains anything there are lots of opinions on how to properly type it. I'm actively seeking help here.
As stated earlier, docs now live in the vitedocs directory. Its called vitedocs because I migrated them from storybook to now use a custom singe page react app built with vite... pronounced "veet" by the way :)
Most of the docs contain examples where each example has a directory.
For example, if you look at the vitedocs/Pages/GettingStarted/CarColors you will find the CarColors.jsx file.
This file contains the page that gets shown ( whatever info you want to show on that page ) and then the example itself.
Because we use vite, we can take advantage of the fact that vite can import both a react component normally, and also the raw text from that file! Therefore creating examples is as simple as this!
Note how we make use of the SideBySide component to render our code and example
import Example from './Example';
import exampleCode from './Example.jsx?raw';
//... other stuff
<SideBySide
leftHeader={<h3>Example: </h3>}
rightHeader={<h3>Code:</h3>}
left={<Example />}
right={<Code links input1={exampleCode} />}
/>;
useConditional hook.The docs site exposes raw markdown for LLMs and other tools that prefer plain text over the interactive React UI.
llms.txt — an index of all documentation pages with links to their markdown URLs.md endpoints — append .md to any doc URL to get the raw markdown for that pageIn local dev the same URLs work under the Vite dev server:
No separate markdown files are maintained for doc pages. A Vite plugin (vite-plugins/llmsPlugin.js) generates everything at dev and build time:
vitedocs/llms/parseRoutes.js parses vitedocs/Routes/Routes.jsx to map each doc route to its React component source file.vitedocs/llms/extractMarkdown.js walks each page's JSX and produces markdown: headings, prose (Info blocks, lists, links), and example code from Vite ?raw imports. README.md and CHANGELOG.md are served as-is for their routes.llms.txt and .md URLs on demand.npm run build:docs writes static llms.txt and .md files into docs/ alongside the built SPA for GitHub Pages.Extraction is best-effort for JSX pages (interactive demos and some UI chrome are omitted), but it is enough for LLMs to consume the docs without duplicating content.
322 followers · starred Jan 2020
277 followers · starred Apr 2019
414 followers · starred Jun 2022
225 followers · starred Jun 2022