An experimental React form library using lenses. Lenses are a concept from functional programming. They are modular data accessors that play nice with immutable data. A big advantage of lens implementation in this library is that they are self-similar, so you can create reusable form components at any level of nesting in your application state.
This library is heavily inspired by André Staltz's use-profunctor-state.
See src/Example.js
for an example implementation.
npm install fractal-form
import { memo, useState } from "react"
import { useFractalForm, useLensField } from "fractal-form"
const Input = memo(({ label, value, onChange }) => (
<label>{label}:
<input value={value} onChange={onChange} />
</label>
))
export const Basic = () => {
const [submittedValues, setSubmittedValues] = useState({})
const {useFormLens, value} = useFractalForm({ name: '' })
const [nameField] = useLensField(useFormLens, 'name')
const submitForm = () => setSubmittedValues(value)
return (
<div>
<Input label="Name" {...nameField} />
<button onClick={submitForm}>Submit form</button>
<pre>{JSON.stringify(submittedValues, null, 2)}</pre>
</div>
)
}
const initialValues = { name: 'Bob' }
const {
form, value, error, touched,
setForm, setValues, setErrors, setTouched,
useFormLens, useValuesLens, useErrorsLens, useTouchedLens,
} = useFractalForm(initialValues)
Create a form object for a given initial state.
-
form
is the entire form state, an object composed of keys{ value, error, touched }
. -
value
contains the form values. Initially set toinitialValues
. -
error
contains the errors. Initially set to{}
-
touched
contains the touched status of the fields. Initally set to{}
-
set*
are functions that set the given object outright. You probably should avoid using those. -
use*Lens
are hooks which provide lenses to each one of the properties. They are the same asuseLens
hooks returned fromuseLensState
(see below).
const validateName = (_parentValue, name) => name.length < 5 ? "Name too short" : null
const { useFormLens } = useFractalForm({ name: 'Bob' })
const [nameField, setNameField, useNameFieldLens] = useLensField(useFormLens, 'name', validateName)
return (
<input {...nameField} />
)
This hook creates a lens that focuses on a particular field name from the value
, touched
, and error
object.
nameField
is a utility object containing thevalue
,error
, andtouched
values for the given field name as well asonChange
(updates the value and validates it) andonBlur
(setstouched
to true) callbacks.setNameField
expects an object of{ value, error, touched }
for the given fielduseNameFieldLens
is a lens for the value, error and touched for the given field. It can be provided as the first argument to anotheruseLensField
const [state, setState, useLens] = useLensState({ name: 'Bob' })
const [name, setName] = useLens(s => s.name, (s, a) => ({ ...s, name: a }))
console.log(state) // -> { name: 'Bob' }
console.log(name) // -> 'Bob'
setName('Alice')
console.log(name) // -> 'Alice'
console.log(state) // -> { name: 'Alice' }
setState({ name: 'Charles' })
console.log(name) // -> 'Charles'
This hook is exactly the same as React's useState
except it adds another element to the array. useLens
is a hook that takes two functions:
view: s => a
takes a full states
and returns a partial statea
update: (s, a) => t
takes a full states
and a new partial statea
and returns a new full statet
The two states are going to be synchronised.
The lenses are memoized so most of the time wrapping a component which uses them in React.memo
should prevent unnecessary rerenders.
const [state, setState, useLens] = useLensState({ name: 'Bob' })
const [name, setName] = useLens(...lensForProp('name'))
A helper which takes a key name returns a pair of functions [view, update]
for that key name.