Elegant cross-platform form management primitives
for the react hooks era.
Working with forms on react can be a really repetitive task. Most of the existing abstractions provides a render props API and it is just not cool on the react hooks era.
Also, some of those packages does not provide out of the box support for both web and mobile platforms.
Formal is a cross-platform solution that exposes just the right primitives you need to manage your forms state and validations with ease.
Add this package to your application dependencies
with yarn:
yarn add @kevinwolf/formal
# npm install --save @kevinwolf/formal
This package exports by default the useFormal
hook, so you only need to import it and hook it up with your initial state and an options.
import React from "react";
import useFormal from "@kevinwolf/formal";
const initialValues = {
firstName: "Tony",
lastName: "Stark",
email: "[email protected]"
};
function App() {
const formal = useFormal(initialValues, {
onSubmit: values => console.log("Your values are:", values)
});
return (
<form onSubmit={formal.handleSubmit}>
<div>
<label htmlFor="firstName">First Name</label>
<input {...formal.getFieldProps("firstName")} type="text" />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<input {...formal.getFieldProps("lastName")} type="text" />
</div>
<div>
<label htmlFor="email">Email</label>
<input {...formal.getFieldProps("email")} type="text" />
</div>
<button type="submit">Subnit</button>
</form>
);
}
import React from "react";
import { View, Text, TextInput, Button } from "react-native";
import useFormal from "@kevinwolf/formal";
const initialValues = {
firstName: "Tony",
lastName: "Stark",
email: "[email protected]"
};
function App() {
const formal = useFormal(initialValues, {
onSubmit: values => console.log("Your values are:", values)
});
return (
<View>
<View>
<Text>First Name</Text>
<TextInput {...formal.getFieldProps("firstName")} />
</View>
<View>
<Text>Last Name</Text>
<TextInput {...formal.getFieldProps("lastName")} />
</View>
<View>
<Text>Email</Text>
<TextInput {...formal.getFieldProps("email")} />
</View>
<Button title="Submit" onPress={formal.handleSubmit} />
</View>
);
}
Main useFormal hook. Use it on your functional components to get the primitives to easily work with forms.
export default function useFormal<FormalValues>(
initialValues: FormalValues,
config: FormalConfig<Values>
): FormalState<Values>;
The form initial values. It can be a hardcoded object or an object gotten from an API endpoint.
type InitialValues = {
[field: string]: any;
};
Example:
const initialValues = {
firstName: "Tony",
lastName: "Stark",
email: "[email protected]"
};
The hook configuration object.
import { Schema } from "yup";
interface FormalConfig<FormalValues> {
schema?: Schema<FormalValues>;
onSubmit: (
values: FormalValues,
formal: FormalState<Values>
) => void | Promise<any>;
}
A yup schema definition. It will be called before submitting the form.
The function that will be called if your form is correctly validated, passing the actual values as the first argument and the FormalState as the second argument. If it is an asynchronous function, then formal.isLoading
will be true until the promise is resolved or rejected.
Example:
import * as yup from "yup";
const schema = yup.object().shape({
firstName: yup.string().required(),
lastName: yup.string().required(),
email: yup
.string()
.email()
.required()
});
async function onSubmit(values, formal) {
try {
await someAsyncTask(values);
} catch (errors) {
const formattedErrors = transformErrorsForFormal(errors);
formal.setErrors(formattedErrors);
}
}
const formalConfig = {
schema,
onSubmit
};
This is the state, callbacks, flags and prop getters returned by useFormal() hook.
interface FormalState<Values> {
// Flags
isValid: boolean;
isDirty: boolean;
isSubmitting: boolean;
isSubmitted: boolean;
// State
values: Values;
errors: null | FormalErrors<Values>;
// Callbacks
handleChange: (field: keyof Values, value: any) => void;
handleReset: () => void;
handleSubmit: () => void;
setErrors: (errors: FormalErrors<Values>) => void;
// Prop getters
getFormProps: () => FormalFormProps;
getFieldProps: (field: keyof Values) => FormalFieldProps;
getResetButtonProps: () => FormalResetButtonProps;
getSubmitButtonProps: () => FormalSubmitButtonProps;
}
A boolean indicating if the schema is valid or not.
A boolean indicating if the form has changed since is initial state or its latest successful submission.
A boolean indicating if the form is being submitted.
A boolean indicated if the form has already been submitted.
The current form values.
The current form errors.
Programatically change the value of a field.
formal.handleChange("firstName", "New First Name");
Programatically reset the form to its initial state or last successful values in case it has been submitted.
formal.handleReset();
Programatically submit the form.
formal.handleSubmit();
Returns the props to spread to a form element. Wraps formal.handleSubmit
method.
NOTE: Since React Native does not have a
<form />
equivalent, this method will throw an error.
Example:
formal.getFormProps = () => ({
onSubmit: e => {
e.preventDefault();
formal.handleSubmit();
};
})
<form {...formal.getFormProps()} />;
Returns the props to spread to a form field. Wraps formal.values[field]
value and formal.handleChange
method.
formal.getFieldProps = field => ({
name: field,
id: field,
value: formal.values[field],
// On React Web:
onChange: e => {
e.preventDefault();
formal.handleChange(field, e.target.value);
},
// On React Native:
onChangeText: text => {
formal.handleChange(field, text);
}
});
// React Web:
<input {...getInputProps('firstName')} type="text" />
// React Native:
<TextInput {...getInputProps('firstName')} />
Useful if you have a reset button on your form. Wraps handleReset
method.
formal.getResetButtonProps = () => ({
// On React Web:
onClick: () => {
formal.handleReset();
},
// On React Native:
onPress: () => {
formal.handleReset();
}
});
// React Web:
<button {...formal.getResetButtonProps()}>Reset form</button>
// React Native:
<Button {...formal.getResetButtonProps()} title="Reset form" />
Returns the props to spread to a submit button. Wraps formal.isValid
, formal.isDirty
and formal.isSubmitting
flags and formal.handleSubmit
method.
formal.getSubmitButtonProps = () => ({
disabled: !formal.isValid || !formal.isDirty || formal.isSubmitting,
// On React Web:
onClick: () => {
formal.handleSubmit()
},
// On React Native:
onPress: () => {
formal.handleSubmit();
}
});
// React Web:
<button {...formal.getSubmitButtonProps()}>Submit form</button>
// React Native:
<Button {...formal.getSubmitButtonProps()} title="Submit form" />
If you have any question, suggestion or recommendation, please open an issue about it.
If you want to purpose some changes create a pull request.
git clone [email protected]:kevinwolfcr/formal.git && cd $_
.- Install dependencies
yarn install
.
This library uses storybook to visually document itself.
To run the tests run yarn test
or yarn test --watch
to watch for changes. Please write some tests if you are changing anything 🙏🏻.
Although this is connected to TravisCI, it's encourage to run yarn validate
in order to make sure everything works as expected.
This is heavily inspired on formik, which currently does not support react hooks. If you are aware or maintain a similar solution, please let me know.