Skip to content

Commit c7018ea

Browse files
committed
Submit helper
1 parent 237f4ab commit c7018ea

File tree

4 files changed

+55
-13
lines changed

4 files changed

+55
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@
1919
2020
# 1.2.9 (03/04/2021)
2121
22-
- Do not reset values on useForm state change, because this can cause confusion.
22+
- Do not reset values on `useForm` state change, because this can cause confusion.
23+
- `form.handleSubmit` helper function.

example/src/App.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ export function ArrayTest() {
9797
const [values, setValues] = useState({ name: "a list", items: ["asdf"] });
9898
const form = useForm(values);
9999
const arrayForm = useArrayForm(form, "items");
100-
101100
return (
102101
<form
103102
onSubmit={(ev) => {
@@ -137,19 +136,11 @@ export function Form() {
137136

138137
return (
139138
<form
140-
onSubmit={async (ev) => {
141-
ev.preventDefault();
142-
143-
await form.validate(); // Validate manually when validateOnChange is disabled.
144-
if (form.error) return; // Do not submit if errors
145-
146-
form.setState({ isSubmitting: true }); // Set the form state (updates every component listening for state updates)
147-
139+
onSubmit={form.handleSubmit(async () => {
148140
await new Promise((res) => setTimeout(res, 1000)); // Fake fetch
149141

150-
form.setState({ isSubmitting: false }); // Set the form state (updates every component listening for state updates)
151142
form.setDefaultValues(form.values); // Set new default values
152-
}}
143+
})}
153144
>
154145
<div style={{ display: "grid", gridTemplateColumns: "60% 40%", gridTemplateRows: "100%", gap: "2em", margin: "2em" }}>
155146
<VisualRender>
@@ -342,7 +333,7 @@ export function Form() {
342333
form={form}
343334
render={({ state, dirty }) => (
344335
<div style={{ margin: "0.5em 0" }}>
345-
<button style={{ fontSize: "1.3em" }} disabled={state.isSubmitting || !dirty}>
336+
<button type="submit" style={{ fontSize: "1.3em" }} disabled={state.isSubmitting || !dirty}>
346337
Submit
347338
</button>
348339
<button

example/src/TestForm.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React, { useState } from "react";
2+
import { FormInput, useForm } from "typed-react-form";
3+
4+
export function TestForm() {
5+
const [counter, setCounter] = useState(0);
6+
const form = useForm({ firstName: "Stijn", lastName: "Rogiest" });
7+
8+
return (
9+
<form>
10+
<label>FirstName</label>
11+
<FormInput form={form} name="firstName" />
12+
<label>Lastname</label>
13+
<FormInput form={form} name="lastName" />
14+
<pre>Counter = {counter}</pre>
15+
<button type="button" onClick={() => setCounter(counter + 1)}>
16+
Increase
17+
</button>
18+
</form>
19+
);
20+
}

src/form.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,13 @@ export class FormState<T, State = DefaultState, Error extends string = DefaultEr
241241
* @param notifyParent Should this form notify the parent form about this change?
242242
*/
243243
public setValues(values: T, validate?: boolean, isDefault: boolean = false, notifyChild: boolean = true, notifyParent: boolean = true) {
244+
// No set is used because this could cause problems with array keys, which must always be in the right order
244245
let keys = Object.keys(isDefault ? this.defaultValues : this.values);
245246
let newKeys = Object.keys(values);
246247
for (let i = 0; i < newKeys.length; i++) {
247248
if (!keys.includes(newKeys[i])) keys.push(newKeys[i]);
248249
}
250+
249251
// Traverse backwards, so when removing array items, the whole array gets shifted in the right direction
250252
for (let i = keys.length - 1; i >= 0; i--) {
251253
let key = keys[i] as keyof T;
@@ -409,6 +411,34 @@ export class FormState<T, State = DefaultState, Error extends string = DefaultEr
409411
if (notifyParent) this.updateParentState();
410412
}
411413

414+
/**
415+
* Creates a submit handler to pass to your `<form onSubmit={...}>`. The function executes the passed handler only if the form validates correctly.
416+
* @param handler The handler to execute when this form contains no errors.
417+
*/
418+
public handleSubmit(handler: (form: FormState<T, State, Error>) => void | Promise<void>) {
419+
async function handle(this: FormState<T, State, Error>, ev: React.FormEvent<HTMLFormElement>) {
420+
ev.preventDefault();
421+
422+
// Show helpful warning when using buttons to submit
423+
if (process.env.NODE_ENV === "development") {
424+
let buttons = Array.from((ev.target as HTMLFormElement).querySelectorAll("button"));
425+
let noTypeButton = buttons.find((e) => !("type" in e.attributes));
426+
if (noTypeButton) {
427+
console.error(
428+
`The submitted form contains a button without a type attribute. Please populate every button in your form with either type="button" or type="submit".`,
429+
noTypeButton
430+
);
431+
}
432+
}
433+
434+
if (!(await this.validate())) return;
435+
this.setState({ ...this.state, isSubmitting: true });
436+
await handler(this);
437+
this.setState({ ...this.state, isSubmitting: false });
438+
}
439+
return handle.bind(this);
440+
}
441+
412442
/**
413443
* Listen for changes on a field, will trigger when value, defaultValue, dirty and error changes for a field. Make sure you pass its return value back to `ignore()` after you are done listening.
414444
* @param key The field to listen to.

0 commit comments

Comments
 (0)