Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
react-form
Advanced tools
React hooks for managing form state and lifecycle
React Form is currently in alpha! This means:
$ yarn add react-form
# or
$ npm i react-form --save
Next, let's build a reusable input field.
import { useField, splitFormProps } from 'react-form'
const InputField = React.forwardRef((props, ref) => {
// Let's use splitFormProps to get form-specific props
const [field, fieldOptions, rest] = splitFormProps(props)
// Use the useField hook with a field and field options
// to access field state
const {
meta: { error, isTouched, isValidating },
getInputProps,
} = useField(field, fieldOptions)
// Build the field
return (
<>
<input
{...getInputProps({ ref, ...rest })}
// This will give us the following props:
// {
// value: field.value,
// onChange: e => field.setValue(e.target.value),
// onBlur: e => field.setMeta({ isTouched: true }),
// ref,
// ...rest
// }
//
// You can always wire this up on your own, but prop
// getters are great for this!
/>
{/*
Let's inline some validation and error information
for our field
*/}
{isValidating ? (
<em>Validating...</em>
) : isTouched && error ? (
<em>{error}</em>
) : null}
</>
)
})
Now that we have an input field, we can build our form!
function MyForm() {
// Memoize some default values
const defaultValues = React.useMemo(
() => ({
name: 'Tanner',
address: {
street: '123 React Road',
},
}),
[]
)
// Use the useForm hook to create a form instance
const {
Form,
meta: { isSubmitting, canSubmit },
} = useForm({
defaultValues,
onSubmit: async (values, instance) => {
// onSubmit (and everything else in React Form)
// has async support out-of-the-box
await sendToServer(values)
// The entire up-to-date form api is
// always available, everywhere
instance.reset()
},
})
return (
<Form>
<label>
Name: <InputField field="name" />
</label>
<label>
Address Street:{' '}
<InputField
field="address.street"
validate={value => (!value ? 'Baz is required!' : false)}
/>
</label>
{isSubmitting ? 'Submitting...' : null}
<button type="submit" disabled={!canSubmit}>
Submit
</button>
</Form>
)
}
useForm
import { useForm } from 'react-form'
const instance = useForm(options)
defaultValues: any
onSubmit(values, instance) => Promise
onSubmit
is called when the form's Form element is submitted or when instance.handleSubmit
is called.values
is the submitted values object for the forminstance
is the latest version of the form instance (the same instance that is returned from useForm
)instance.isSubmitted
will be set to true
instance.isSubmitting
will be set to false
validate async (values, instance) => String | false | undefined
validate
is an asynchronous function that is called when the form becomes dirty and is given the opportunity to set/clear/update errors and/or manage general form stateinstance
is the latest version of the form instance (the same instance that is returned from useForm
)useField
validatePristine: Bool
validatePristine
option to true
An object
with the following components, properties and methods:
Form: ReactComponent<form>
form
element.onSubmit
.
useField
instances inside it.values: any
meta: Object
error: String | any
validation
function, or if it is set programmatically, it will be stored here.isSubmitting: Bool
true
if the form is currently submittingisDirty: Bool
true
if the form is dirtyisSubmitted: Bool
true
if the form has been submitted successfullysubmissionAttempts: Int
0
, every time a form submission is attemptedisValid: Bool
true
if isDirty === true
and isValid === true
, meaning that the form has been touched and there are no field-level or form-level errorscanSubmit: Bool
true
if isValid === true
and isSubmitting === false
...any
field.setMeta
or instance.setFieldMeta
functionsformContext: FormInstance
useField
instances to a parent form. This is useful if useField
is in the same block scope as useForm
.formContext
to useField
like so: useField(fieldName, { formContext })
. That field is now linked to the form that provided the formContext
.debugForm: Bool
true
the form instance will be serialized and rendered after the Form
element returned by the instancereset() => void
instance.values
to the defaultValues
optionsetMeta((updater(previousMeta: Object) => newMeta: Object) | newMeta: Object)
meta
value.setState
callback style.handleSubmit(formSubmitEvent) => void
Form
component returned by useTable
.form
element, this function is required and should be set as the callback to the form
via the onSubmit
propdebounce(Function, wait: Int) => Promise
validate
callback to control validation timing and flowsetValues(updater(previousValues: Object) => newValues: Object) | newValues: Object
meta
value.setState
callback style.setState
callback style.runValidation() => void
getFieldValue(fieldPath: String) => any
value
from the form's instance.values
object located at the fieldPath
string that was passed.fieldPath
is a string that supports object notation, eg. foo[3].bar[1].baz
getFieldMeta(fieldPath: String) => Object: { error: String | null, ...any }
meta
object from the form.fieldPath
is a string that supports object notation, eg. foo[3].bar[1].baz
setFieldValue(fieldPath: String, (updater(previousValue: any) => newValue: any) | newValue: any, options: Object { isTouched: Bool } )
value
that is stored in the instance.values
object.setState
callback style.options
object can be passed.
isTouched: Bool
true
false
, this operation will not trigger the field's isTouched
to automatically be set to true
setFieldMeta(fieldPath: String, (updater(previousMeta: Object) => newMeta: Object) | newMeta: Object)
meta
value.setState
callback style.pushFieldValue(fieldPath: String, newValue: any)
insertFieldValue(fieldPath: String, insertIndex: Int, value: any)
removeFieldValue(fieldPath: String, removalIndex: Int)
swapFieldValues(fieldPath: String, firstIndex: Int, secondIndex: Int)
useField
import { useField } from 'react-form'
const fieldInstance = useField(fieldPath, options)
fieldPath: String
foo[3].bar[1].baz
1
, [1]
, 1.
, .1.
or .1
options
- An optional object to configure the field
defaultValue: any
defaultValue
to set the default value
state for the field.React.useMemo
to make this object only change when necessarydefaultError: String | undefined
defaultError
to set the default error
state for the field.React.useMemo
to make this object only change when necessarydefaultIsTouched: Bool | undefined
defaultIsTouched
to set the default isTouched
state for the field.React.useMemo
to make this object only change when necessarydefaultMeta: Object | undefined
defaultMeta
to set any additional default meta
state for the field.defaultValue
, defaultError
and defaultIsTouched
, changing this object will not trigger the field meta to be updated. It is only updated when the useField
hook mounts and meta
for that field has not yet been initialized (meta === undefined
)validate async (value, instance) => String | false | undefined
validate
is an asynchronous function that is called when the field becomes dirty and is given the opportunity to set/clear/update errors and/or manage general field metainstance
is the latest version of the field instance (the same instance that is returned from useField
)useForm
filterValue: (value, instance) => newValue
filterValue
function is used to manipulate new values before they are set via field.setValue
and instance.setFieldValue
.validatePristine: Bool
validatePristine
option to true
An object
with the following components, properties and methods:
form: FormInstance
instance
fieldName: String
value: any
meta: Object {}
error: String | any
validation
function, or if it is set programmatically, it will be stored here.isTouched: Bool
true
if the field has been touched....any
field.setMeta
or instance.setFieldMeta
functionsFieldScope: ReactComponent<Provider>
fieldpath
useField
instances inside it.useField(fieldPath)
instances used insde of FieldScope
will inherit this field's fieldPath
as a parent.debounce(Function, wait: Int) => Promise
useField
instance.validate
callback to control validation timing and flowrunValidation() => void
The following methods do not require the use of a fieldPath
. This field's fieldPath
will automatically be used.
setValue((updater(previousValue: any) => newValue: any) | newValue: any, options: Object { isTouched: Bool } )
value
that is stored in the instance.values
object.setState
callback style.options
object can be passed.
isTouched: Bool
true
false
, this operation will not trigger the field's isTouched
to automatically be set to true
setMeta((updater(previousMeta: Object) => newMeta: Object) | newMeta: Object)
meta
value.setState
callback style.pushValue(newValue: any)
insertValue(insertIndex: Int, value: any)
removeValue(removalIndex: Int)
swapValues(firstIndex: Int, secondIndex: Int)
The following methods are almost exactly the same as their top-level form instance
counterparts, except for any fieldPath
that is passed to them will be prefixed with this field's fieldPath
For example, if our field had the fieldPath
of foo
, then setFieldValue('[0]', true)
would be similar to calling instance.setFieldValue('foo[0]', true)
setFieldValue(subFieldPath, ...)
- See Form InstancesetFieldMeta(subFieldPath, ...)
- See Form InstancepushFieldValue(subFieldPath, ...)
- See Form InstanceinsertFieldValue(subFieldPath, ...)
- See Form InstanceremoveFieldValue(subFieldPath, ...)
- See Form InstanceswapFieldValues(subFieldPath, ...)
- See Form InstanceIf you don't need to perform any async validation in your form or field, you can just return an error string directly (or false
clear an error):
string
, the value returned will be stored in either the form's instance.meta.error
or the field's meta.error
valuefalse
, the error in either the form's instance.meta.error
or the field's meta.error
value the will be set to null
undefined
, no changes will happenconst options = {
validate: value => {
// To set an error:
if (!somethingIsWrong) {
return 'This form/field has a form-level error'
}
// To clear any errors:
return false
},
}
Asynchronous validation is as easy as returning a promise that resolves to the standard return types shown above in the synchronous validation example:
const options = {
validate: async value => {
const error = await validateOnServer(values)
if (error) {
return error
}
return false
},
}
You also mix both synchronous and asynchronous validation easily with this pattern as well:
const options = {
validate: async value => {
// First check for synchronous errors
if (!values.foo || !values.bar) {
return 'Foo and bar are required!'
}
// Then return a promise that resolves any async errors
const error = await validateOnServer(values)
return error ? error : false
},
}
If you're validation is firing too often, you can debounce any stage of your validation function (sync or async) with React Form's built-in debounce utility. instance.debounce
returns a promise that only resolves for the latest call after a given amount of time. This way, any outdated validation attempts are discarded automatically.
To debounce synchronous validation, return the promise from debounce
, called with a synchronous function:
const options = {
validate: (values, instance) => {
return instance.debounce(() => {
// Wait 1000 milliseconds before validating anything
if (!values.foo || !values.bar) {
return 'Foo and bar are required!'
}
return false
}, 1000)
},
}
To debounce asynchronous validation, return the promise from debounce
, called with an asynchronous function:
const options = {
validate: async (values, instance) => {
return instance.debounce(async () => {
// Wait 2 seconds before validating on the server
const error = await validateOnServer(values)
return error ? error : false
}, 2000)
},
}
Again, you can mix both sync/async and immediate/debounced behavior however you'd like!
Pro Tip: This is my favorite and recommended approach to mixed validation.
const options = {
validate: async (values, instance) => {
// Check for synchronous errors immediately without debouncing them
if (!values.foo || !values.bar) {
return 'Foo and bar are required!'
}
// Then, if sync validation passes
return instance.debounce(() => {
// Wait 2 seconds before validating on the server
const error = await validateOnServer(values)
return error ? error : false
}, 2000)
}
}
meta
and field meta
Returning an error string or false from validate is simply shorthand for setting/unsetting the error
property on either the form's instance.meta
object or a field's meta
object. If you don't want to set an error and would rather set a success or warning message, you can use the instance.setMeta
(for form-level validation) or the instance.setMeta
function (for field-level validation). More than just the error field can be set/used on both the instance.meta
object and each individual field's meta
object. You could use this meta information for success messages, warnings, or any other information about a field. Only the error
and isTouched
meta properties are used internally by React Form to determine form validity.
const options = {
validate: async (values, instance) => {
const serverError = await validateOnServer(values)
if (serverError) {
setMeta({
error: serverError,
message: null,
errorStack: serverError.stack,
})
} else {
setMeta({
error: null,
message: 'The form is good to be submitted!',
errorStack: null,
})
}
// Make sure this function returns undefined if you are handling
// meta manually.
},
}
Field scoping is useful for building form inputs that don't require knowledge of the parent field name. Imagine a field component for managing some notes on a form:
function NotesField({ field }) {
const fieldInstance = useField(field)
// ...
}
This approach would required us to define the nested field
when we use the component:
function MyForm() {
const { Form } = useForm()
return (
<Form>
<NotesField field="notes" />
</Form>
)
}
This isn't a problem for shallow forms, but if we are in a deeply nested part of a form UI, it get's more verbose:
function MyForm() {
const { Form } = useForm()
return (
<Form>
<ConfigField field="config" />
</Form>
)
}
function ConfigField({ field: parentField }) {
return (
<>
<NotesField field={`${parentField}.notes`} />
<OtherField field={`${parentField}.other`} />
<FooField field={`${parentField}.foo`} />
</>
)
}
Instead of requiring that all deep fields be composed with their parent strings, you can use the FieldScope
component returned by the useField
hook to create a new field scope for any useField
instances rendered inside of it:
function MyForm() {
const { Form } = useForm()
return (
<Form>
<ConfigField field="config" />
</Form>
)
}
function ConfigField({ field: parentField }) {
const { FieldScope } = useField('config')
return (
<FieldScope>
<NotesField field="notes" />
<OtherField field="other" />
<FooField field="foo" />
</FieldScope>
)
}
Using this approach, you can avoid having to compose deeply nested field names!
useFormContext
A hook for gaining access to the form state from within a Form
component
import { useFormContext } from 'react-form'
function App() {
const { Form } = useForm()
return (
<Form>
<Stuff />
</Form>
)
}
function Stuff() {
const formInstance = useFormContext()
console.log(formInstance)
}
splitFormProps
A utility function for filter React-Form-related props from an object.
import { splitFormProps } from 'react-form'
function TextField(props) {
const [field, options, rest] = splitFormProps(props)
// options === {
// defaultValue,
// defaultIsTouched,
// defaultError,
// defaultMeta,
// validatePristine,
// validate,
// onSubmit,
// defaultValues,
// debugForm,
// }
const fieldInstance = useField(field, options)
return <input {...rest} />
}
If you get into a situation where you need to use useForm
and useField
in the same block scope, you may see a missing form context error. This is because your useField
usage is not inside of a <Form>
component. To get around this error for this use case, you can pass the form's instance.formContext
value to the useField
options to manually link them together:
function App() {
const { Form, formContext } = useForm()
// This field will now be manually linked to the form above
const fieldInstance = useField('age', {
formContext,
})
return <Form>...</Form>
}
FAQs
⚛️ 💼 React hooks for managing form state and lifecycle
The npm package react-form receives a total of 8,052 weekly downloads. As such, react-form popularity was classified as popular.
We found that react-form demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.