Skip to content

Commit 3e9bee7

Browse files
committed
Constraints
1 parent 844b662 commit 3e9bee7

File tree

8 files changed

+53
-40
lines changed

8 files changed

+53
-40
lines changed

src/Components.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useEffect, useRef, useState } from "react";
2-
import { ChildFormState, DirtyMap, ErrorMap, FormState } from "./form";
1+
import React from "react";
2+
import { ChildFormState, DefaultError, DefaultState, DirtyMap, ErrorMap, FormState } from "./form";
33
import { useArrayForm, useListener, useAnyListener, useChildForm, useTruthyListener } from "./hooks";
44

55
/**
@@ -9,11 +9,11 @@ import { useArrayForm, useListener, useAnyListener, useChildForm, useTruthyListe
99
* @param parent The parent form.
1010
* @param name The parent's field to create a child form for.
1111
*/
12-
export function ArrayForm<Parent, ParentState, ParentError, Key extends keyof Parent>(props: {
12+
export function ArrayForm<Parent, Key extends keyof Parent, ParentState = DefaultState, ParentError extends string = DefaultError>(props: {
1313
form: FormState<Parent, ParentState, ParentError>;
1414
name: Key;
1515
render?: (props: {
16-
form: ChildFormState<Parent, ParentState, ParentError, Key>;
16+
form: ChildFormState<Parent, Key, ParentState, ParentError>;
1717
remove: (index: number) => void;
1818
clear: () => void;
1919
move: (index: number, newIndex: number) => void;
@@ -40,7 +40,7 @@ export function ArrayForm<Parent, ParentState, ParentError, Key extends keyof Pa
4040
* @param form The form to listen on.
4141
* @param name The form's field to listen to.
4242
*/
43-
export function Listener<T, State, Error, Key extends keyof T>(props: {
43+
export function Listener<T, Key extends keyof T, State = DefaultState, Error extends string = DefaultError>(props: {
4444
form: FormState<T, State, Error>;
4545
name: Key;
4646
render?: (props: {
@@ -63,7 +63,7 @@ export function Listener<T, State, Error, Key extends keyof T>(props: {
6363
* You shouldn't use this hook in large components, as it rerenders each time something changes. Use the wrapper <AnyListener /> instead.
6464
* @param form The form to listen to.
6565
*/
66-
export function AnyListener<T, State, Error>(props: {
66+
export function AnyListener<T, State = DefaultState, Error extends string = DefaultError>(props: {
6767
form: FormState<T, State, Error>;
6868
render?: (props: FormState<T, State, Error>) => React.ReactNode;
6969
}) {
@@ -78,10 +78,10 @@ export function AnyListener<T, State, Error>(props: {
7878
* @param parentForm The parent form.
7979
* @param name The parent's field to create a child form for.
8080
*/
81-
export function ChildForm<Parent, ParentState, ParentError, Key extends keyof Parent>(props: {
81+
export function ChildForm<Parent, Key extends keyof Parent, ParentState = DefaultState, ParentError extends string = DefaultError>(props: {
8282
form: FormState<Parent, ParentState, ParentError>; // Use the parent prop instead of the form prop when using ChildForm.
8383
name: Key;
84-
render?: (props: ChildFormState<Parent, ParentState, ParentError, Key>) => React.ReactNode;
84+
render?: (props: ChildFormState<Parent, Key, ParentState, ParentError>) => React.ReactNode;
8585
}) {
8686
const childForm = useChildForm(props.form, props.name);
8787
// Causes a rerender when the object value becomes null/undefined

src/elements/FormError.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import React, { HTMLAttributes } from "react";
2-
import { FormState } from "../form";
2+
import { DefaultError, FormState } from "../form";
33
import { useListener } from "../hooks";
44

5-
export type FormErrorProps<T, Error, Key extends keyof T> = Omit<HTMLAttributes<HTMLParagraphElement>, "name" | "form"> & {
5+
export type FormErrorProps<T, Key extends keyof T, Error extends string = DefaultError> = Omit<
6+
HTMLAttributes<HTMLParagraphElement>,
7+
"name" | "form"
8+
> & {
69
form: FormState<T, any, Error>;
710
name: Key;
811
};
@@ -14,7 +17,7 @@ export type FormErrorProps<T, Error, Key extends keyof T> = Omit<HTMLAttributes<
1417
*
1518
* When this error component is too basic for your needs, you can always [create your own](https://github.com/CodeStix/typed-react-form/wiki/FormError#custom-error-component).
1619
*/
17-
export function FormError<T, Error, Key extends keyof T>({ form, name, ...rest }: FormErrorProps<T, Error, Key>) {
20+
export function FormError<T, Key extends keyof T, Error extends string = DefaultError>({ form, name, ...rest }: FormErrorProps<T, Key, Error>) {
1821
const { error } = useListener(form, name);
1922
if (!error || typeof error === "object") return null;
2023
return <p {...rest}>{error + ""}</p>;

src/elements/FormInput.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useMemo } from "react";
22
import { InputHTMLAttributes } from "react";
3-
import { DefaultState, FormState } from "../form";
3+
import { DefaultError, DefaultState, FormState } from "../form";
44
import { useListener } from "../hooks";
55

66
type BaldInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "form" | "value" | "type">;
@@ -32,7 +32,7 @@ export type FormInputType =
3232
| "tel"
3333
| "range";
3434

35-
export type FormInputProps<T, State, Error, Key extends keyof T, Value extends T[Key] | T[Key][keyof T[Key]]> = BaldInputProps & {
35+
export type FormInputProps<T, State, Error extends string, Key extends keyof T, Value extends T[Key] | T[Key][keyof T[Key]]> = BaldInputProps & {
3636
form: FormState<T, State, Error>;
3737
name: Key;
3838
type?: FormInputType;
@@ -55,7 +55,13 @@ export type FormInputProps<T, State, Error, Key extends keyof T, Value extends T
5555
*
5656
* When this component does not satisfy your needs, you can always [create your own](https://github.com/CodeStix/typed-react-form/wiki/Custom-inputs#example-custom-input).
5757
*/
58-
export function FormInput<T, State extends DefaultState, Error, Key extends keyof T, Value extends T[Key] | T[Key][keyof T[Key]]>({
58+
export function FormInput<
59+
T,
60+
Key extends keyof T,
61+
Value extends T[Key] | T[Key][keyof T[Key]],
62+
State extends DefaultState = DefaultState,
63+
Error extends string = DefaultError
64+
>({
5965
form,
6066
name,
6167
style,

src/elements/FormSelect.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, { SelectHTMLAttributes } from "react";
2-
import { DefaultState, FormState } from "../form";
2+
import { DefaultError, DefaultState, FormState } from "../form";
33
import { DEFAULT_DIRTY_CLASS, DEFAULT_ERROR_CLASS, getClassName } from "./FormInput";
44
import { useListener } from "../hooks";
55

6-
export type FormSelectProps<T, State, Error> = Omit<SelectHTMLAttributes<HTMLSelectElement>, "form" | "name"> & {
6+
export type FormSelectProps<T, State, Error extends string> = Omit<SelectHTMLAttributes<HTMLSelectElement>, "form" | "name"> & {
77
form: FormState<T, State, Error>;
88
name: keyof T;
99
errorClassName?: string;
@@ -21,7 +21,7 @@ export type FormSelectProps<T, State, Error> = Omit<SelectHTMLAttributes<HTMLSel
2121
*
2222
* When this component does not satisfy your needs, you can always [create your own](https://github.com/CodeStix/typed-react-form/wiki/Custom-inputs#example-custom-input).
2323
*/
24-
export function FormSelect<T, State extends DefaultState, Error>({
24+
export function FormSelect<T, State extends DefaultState = DefaultState, Error extends string = DefaultError>({
2525
form,
2626
name,
2727
errorClassName,

src/elements/FormTextArea.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, { TextareaHTMLAttributes } from "react";
2-
import { DefaultState, FormState } from "../form";
2+
import { DefaultError, DefaultState, FormState } from "../form";
33
import { DEFAULT_DIRTY_CLASS, DEFAULT_ERROR_CLASS, getClassName } from "./FormInput";
44
import { useListener } from "../hooks";
55

6-
export type FormTextAreaProps<T, State, Error> = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "form" | "name"> & {
6+
export type FormTextAreaProps<T, State, Error extends string> = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "form" | "name"> & {
77
form: FormState<T, State, Error>;
88
name: keyof T;
99
errorClassName?: string;
@@ -21,7 +21,7 @@ export type FormTextAreaProps<T, State, Error> = Omit<TextareaHTMLAttributes<HTM
2121
*
2222
* When this component does not satisfy your needs, you can always [create your own](https://github.com/CodeStix/typed-react-form/wiki/Custom-inputs#example-custom-input).
2323
*/
24-
export function FormTextArea<T, State extends DefaultState, Error>({
24+
export function FormTextArea<T, State extends DefaultState = DefaultState, Error extends string = DefaultError>({
2525
form,
2626
name,
2727
errorClassName,

src/form.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,15 @@ export type ListenerCallback = () => void;
22
export type ListenerMap = { [T in string]?: ListenerCallback };
33
export type Validator<T, Error> = (values: T) => ErrorMap<T, Error> | Promise<ErrorMap<T, Error>>;
44

5-
export type ChildFormMap<T, State, Error> = {
6-
[Key in keyof T]?: ChildFormState<T, State, Error, Key>;
5+
export type ChildFormMap<T, State, Error extends string> = {
6+
[Key in keyof T]?: ChildFormState<T, Key, State, Error>;
77
};
88

99
export type DirtyMap<T> = {
1010
[Key in keyof T]?: boolean;
1111
};
1212

13-
type ObjectOrArray = {
14-
[key: string]: any;
15-
[key: number]: any;
16-
};
17-
18-
export type ErrorType<T, Error> = T extends ObjectOrArray ? ErrorMap<T, Error> | Error : Error;
13+
export type ErrorType<T, Error> = T extends object ? ErrorMap<T, Error> | Error : Error;
1914

2015
export type ErrorMap<T, Error> = {
2116
[Key in keyof T]?: ErrorType<T[Key], Error>;
@@ -54,7 +49,7 @@ export function comparePrimitiveObject<T>(a: T, b: T): boolean | undefined {
5449
return false;
5550
}
5651

57-
export class FormState<T, State = DefaultState, Error = DefaultError> {
52+
export class FormState<T, State = DefaultState, Error extends string = DefaultError> {
5853
/**
5954
* The id of this form, for debugging purposes.
6055
*/
@@ -486,7 +481,7 @@ export class FormState<T, State = DefaultState, Error = DefaultError> {
486481
}
487482
}
488483

489-
export class ChildFormState<Parent, ParentState, ParentError, Key extends keyof Parent> extends FormState<
484+
export class ChildFormState<Parent, Key extends keyof Parent, ParentState, ParentError extends string> extends FormState<
490485
NonNullable<Parent[Key]>,
491486
ParentState,
492487
ParentError

src/hooks.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { DefaultState, DefaultError, FormState, ChildFormState, Validator } from
1010
* @param validateOnMount Validate on mount? Optional, default is false.
1111
* @param defaultState The default state for this form. Form state contains custom global states, example: isSubmitting, isLoading ... Optional, default is `{ isSubmitting: false }`.
1212
*/
13-
export function useForm<T, State = DefaultState, Error = DefaultError>(
13+
export function useForm<T, State = DefaultState, Error extends string = DefaultError>(
1414
defaultValues: T,
1515
validator?: Validator<T, Error>,
1616
validateOnChange = false,
@@ -43,8 +43,11 @@ export function useForm<T, State = DefaultState, Error = DefaultError>(
4343
* @param parentForm The parent form.
4444
* @param name The parent's field to create a child form for.
4545
*/
46-
export function useChildForm<T, State, Error, Key extends keyof T>(parentForm: FormState<T, State, Error>, name: Key) {
47-
let c = useRef<ChildFormState<T, State, Error, Key> | null>(null);
46+
export function useChildForm<T, Key extends keyof T, State = DefaultState, Error extends string = DefaultError>(
47+
parentForm: FormState<T, State, Error>,
48+
name: Key
49+
) {
50+
let c = useRef<ChildFormState<T, Key, State, Error> | null>(null);
4851
if (!c.current) {
4952
c.current = new ChildFormState(parentForm, name);
5053
}
@@ -76,7 +79,10 @@ export function useChildForm<T, State, Error, Key extends keyof T>(parentForm: F
7679
* @param form The form to listen on.
7780
* @param name The form's field to listen to.
7881
*/
79-
export function useListener<T, State, Error, Key extends keyof T>(form: FormState<T, State, Error>, name: Key) {
82+
export function useListener<T, Key extends keyof T, State = DefaultState, Error extends string = DefaultError>(
83+
form: FormState<T, State, Error>,
84+
name: Key
85+
) {
8086
const [, setRender] = useState(0);
8187

8288
useEffect(() => {
@@ -102,7 +108,7 @@ export function useListener<T, State, Error, Key extends keyof T>(form: FormStat
102108
* You shouldn't use this hook in large components, as it rerenders each time something changes. Use the wrapper <AnyListener /> instead.
103109
* @param form The form to listen to.
104110
*/
105-
export function useAnyListener<T, State, Error>(form: FormState<T, State, Error>) {
111+
export function useAnyListener<T, State = DefaultState, Error extends string = DefaultError>(form: FormState<T, State, Error>) {
106112
const [, setRender] = useState(0);
107113

108114
useEffect(() => {
@@ -121,11 +127,11 @@ export function useAnyListener<T, State, Error>(form: FormState<T, State, Error>
121127
* @param parentForm The parent form.
122128
* @param name The parent's field to create a child form for.
123129
*/
124-
export function useArrayForm<Parent, ParentState, ParentError, Key extends keyof Parent>(
130+
export function useArrayForm<Parent, Key extends keyof Parent, ParentState = DefaultState, ParentError extends string = DefaultError>(
125131
parentForm: FormState<Parent, ParentState, ParentError>,
126132
name: Key
127133
) {
128-
const form = useChildForm<Parent, ParentState, ParentError, Key>(parentForm, name);
134+
const form = useChildForm(parentForm, name);
129135
const oldLength = useRef(-1);
130136
const [, setRender] = useState(0);
131137

@@ -193,7 +199,10 @@ export function useArrayForm<Parent, ParentState, ParentError, Key extends keyof
193199
* @param form The form to listen on.
194200
* @param name The form's field to listen to.
195201
*/
196-
export function useTruthyListener<T, State, Error, Key extends keyof T>(form: FormState<T, State, Error>, name: Key) {
202+
export function useTruthyListener<T, Key extends keyof T, State = DefaultState, Error extends string = DefaultError>(
203+
form: FormState<T, State, Error>,
204+
name: Key
205+
) {
197206
const oldTruthy = useRef(!!form.values[name]);
198207
const [, setRender] = useState(0);
199208

src/yup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface YupValidationOptions {
1414
context?: any;
1515
}
1616

17-
export function yupValidator<T, Error = string>(
17+
export function yupValidator<T, Error extends string = string>(
1818
yupSchema: any,
1919
options?: YupValidationOptions,
2020
messageTransformer?: (message: string) => Error
@@ -24,7 +24,7 @@ export function yupValidator<T, Error = string>(
2424
await yupSchema.validate(values, options);
2525
return {};
2626
} catch (ex) {
27-
return yupErrorToErrorMap(ex, messageTransformer ?? ((e) => e as any));
27+
return yupErrorToErrorMap(ex, messageTransformer ?? ((e) => e));
2828
}
2929
};
3030
}

0 commit comments

Comments
 (0)