# Form Mate [](https://www.npmjs.com/package/form-mate) [](https://github.com/franciscop/form-mate/actions) [](https://github.com/franciscop/form-mate/blob/master/index.min.js) [](https://github.com/franciscop/form-mate/blob/master/package.json)
A tiny and elegant library to handle forms with React:
```js
import Form from "form-mate";
// { fullname: "Francisco", email: "xxxxxx@francisco.io" }
export default () => (
);
```
Benefits over a plain `` for more advanced error management.
- Sub Component `` for more advanced loading state handling.
## Getting Started
Install `form-mate` with npm:
```
npm install form-mate
```
Import it and use it anywhere in your React project:
```js
import Form from "form-mate";
export default () => (
);
```
## API
### onSubmit
Mandatory prop that accepts a sync or `async` callback. It will receive the values in the form when submitted:
```js
import Form from "form-mate";
export default function Subscribe() {
return (
);
}
// {
// name: "Francisco",
// subscribe: "on", // The default when no "value" is provided
// terms: "accepted",
// gender: "male"
// }
```
It prevents the default action automatically. See [the tests](https://github.com/franciscop/form-mate/blob/master/src/index.test.tsx) for more examples of how the fields are parsed.
To type the form data, annotate the `data` parameter:
```ts
type SubscribeData = { name: string; gender: "female" | "male" };
```
### onError
Optional prop to handle any error happening in the `onSubmit`, ideal for external error management:
```js
import Form from "form-mate";
import toast from "react-hot-toast";
export default () => {
const onSubmit = (data) => {
throw new Error("Aaaaagh");
};
const onError = (error) => toast.error(error.message);
return (
);
};
```
For inline error management, it would be better to use [``](#formerror):
```js
{(msg) => (msg ?
{msg}
: "")}
```
### onChange
Listen to the forms' changes in fields as they happen. It will be triggered on every keystroke, on every click that changes the data, etc:
```js
import { useState } from "react";
import Form from "form-mate";
export default function Preview() {
const [name, setName] = useState("");
return (
);
}
```
It can be useful for e.g. live previews, character counts, or enabling/disabling other parts of the UI based on the current field values.
### autoReset
By default the form is not reset after it's submitted. This prop can make the form to reset **after** the `onSubmit` callback has resolved successfully:
```js
```
Even with this prop, the form will _not_ be reset if the `onSubmit` throws an error.
This prop is very useful when adding new items to a list in succession, [**see codesandbox example**](https://codesandbox.io/s/determined-nightingale-hzmob).
### encType
The encType can be set to `multipart/form-data` to upload files:
```js
import Form from 'form-mate';
export default() => (
);
```
In that case, the argument `data` passed to the `onSubmit` and `onChange` will be an [instance of `FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) instead.
### \
When there's an error on the `onSubmit` function, you can use this component to display it in multiple ways:
```js
import Form from 'form-mate';
export default () => (
{/* A simple message that is displayed only when there's an error */}
There was an issue...
{/* Use the actual error message within a callback */}
{msg => msg ?
{msg}
: ''}
);
```
- `` will display the `.message` property of the error, only when an error was thrown.
- `Hello` will display the "Hello" message only when there's an error. This is useful for e.g. complimentary error icons, or messages, that are not the main thing.
- `{msg => msg ? 'a' : 'b'}` this will _always_ call the callback; if there was an error, its `.message` will be the first argument, and if there was no error it will be empty.
> Still aliased as `import { FormError } from 'form-mate'`, but the `` way is recommended now.
### \
Show the loading state of the form. The form starts "loading" when its submitted, and finishes loading when the onSubmit() callback finishes executing:
```js
import Form from 'form-mate';
export default () => (
Loading...
);
```
- `Hello` will display the "Hello" message only while the form is loading. This is useful for e.g. complimentary messages, loading indicators, etc.
- `{loading => loading ? 'a' : 'b'}` this will _always_ call the callback; if the component is loading it will receive `true` as it's only parameter, if it's not then it'll receive `false`.
> Still aliased as `import { FormLoading } from 'form-mate'`, but the `` way is recommended now.
## Examples
### Add items to a list
A fully working shopping list example ([**see codesandbox**](https://codesandbox.io/s/determined-nightingale-hzmob)):
```js
import React, { useState } from "react";
import Form from "form-mate";
export default function Groceries() {
const [items, setItems] = useState([]);
return (
{items.map((item) => (
{item.text} × {item.quantity}
))}
);
}
```
### Upload files with React
To upload files with React and Axios, you can do it like this:
```js
import Form from 'form-mate';
export default function App() {
const onSubmit = async (data) => {
// Send the data to the server
const headers = { "Content-Type": "multipart/form-data" };
await axios.post("/hello", data, { headers });
};
return (
);
}
```
### Axios and Hot-Toast
The common pattern to handle forms is to submit the data with Axios, so let's do so:
```js
// This should be somewhere in your codebase, let's assume that your
// API returns a body with { error: "message" } when appropriate
import axios from "axios";
axios.interceptors.response.use(
(res) => res.data,
(error) => {
// A better message to show to the user than the default HTTP one
const message = error.response?.data?.error;
if (message) {
logError(message); // Or any other preferred way
throw new Error(message);
}
// No message; just re-throw the previous error (or anything else)
throw error;
}
);
```
Then when handling our form, we just need to add an onSubmit as usual, without try/catch, since it'll already be caught by Form Mate and handed with the `onError`:
```js
import Form from "form-mate";
import axios from "axios";
import toast from "react-hot-toast";
export default function LoginForm() {
const redirect = useRedirect(); // With your favourite routing lib
const onSubmit = async (data) => {
await axios.post("/login", data);
toast.success(`Successfully logged in!`);
redirect("/");
};
// Show the error, wherever it comes from, on a popup
const onError = (error) => toast.error(error.message);
return (
);
}
```
### Conditional fields
Let's say you want a bit of context of where your users are from. So you ask them for their country, and if the country they provide is the USA you also want their state. Otherwise you don't care if in Spain they are from Madrid or Barcelona.
So we want to render the field to select the state only when the user is from the USA, let's see one way of doing it. We create a form-level variable called "showState", which will be set to `true` when in the USA and to false otherwise, and hence render the state selector when set to true.
[**See CodeSandbox**](https://codesandbox.io/s/loving-keller-ef1k27?file=/src/App.js):
```js
export default function RegisterForm() {
// When in the USA, `showState` should be set to `true`. The select
// Defaults to the first country, which is the USA
const [showState, setShowState] = useState(true);
const onChangeCountry = (e) => {
const newCountry = e.target.value;
// If the new value is the same as the old one, React will
// ignore this, so we don't need to manually check with an if()
setShowState(newCountry === "usa");
};
return (
);
}
```
There are few other ways of doing this, like with `
);
}
```