Merge pull request #21981 from Arcath/react-form-2

Update react-form typings to support 2.12.1
This commit is contained in:
Mine Starks
2017-12-12 09:21:52 -08:00
committed by GitHub
6 changed files with 1162 additions and 273 deletions

View File

@@ -1,4 +1,4 @@
// Type definitions for react-form 1.3
// Type definitions for react-form 2.12
// Project: https://github.com/tannerlinsley/react-form#readme
// Definitions by: Cameron McAteer <https://github.com/cameron-mcateer>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
@@ -6,6 +6,7 @@
import * as React from 'react';
// Helper Types
export type FormValue = any;
export type FormError = string | undefined;
export interface Nested<T> {
@@ -13,24 +14,12 @@ export interface Nested<T> {
}
export type FormValues = Nested<FormValue>;
export type Touched = Nested<boolean>;
export type FormErrors = {[key: string]: FormError} | [{[key: string]: FormError}];
export interface FormErrors {
[key: string]: FormError;
}
export type NestedErrors = Nested<FormErrors>;
export type RenderReturn = JSX.Element | false | null;
export interface FormProps {
loadState?(props: FormProps, self: Form): FormState | undefined;
defaultValues?: FormValues;
preValidate?(values: FormValues, state: FormState, props: FormProps, self: Form): FormValues;
validate?(values: FormValues, state: FormState, props: FormProps): FormErrors;
onValidationFail?(values: FormValues, state: FormState, props: FormProps, self: Form): void;
onChange?(state: FormState, props: FormProps, initial: boolean | FormProps, self: Form): void;
saveState?(state: FormState, props: FormProps, self: Form): void;
willUnmount?(state: FormState, props: FormProps, self: Form): void;
preSubmit?(values: FormValues, state: FormState, props: FormProps, self: Form): FormValues;
onSubmit?(values: FormValues, state: FormState, props: FormProps, self: Form): void;
postSubmit?(values: FormValues, state: FormState, props: FormProps, self: Form): void;
}
export interface FormState {
values: FormValues;
touched: Touched;
@@ -39,24 +28,56 @@ export interface FormState {
dirty?: boolean;
}
export const FormDefaultProps: FormProps;
export interface FormProps {
dontValidateOnMount?: boolean;
defaultValues?: FormValues;
onSubmit?(values: FormValues, submissionEvent: React.SyntheticEvent<any>, formApi: FormApi): void;
preSubmit?(values: FormValues, formApi: FormApi): FormValues;
onSubmitFailure?(errors: FormErrors, formApi: FormApi): void;
formDidUpdate?(formState: FormState): void;
preValidate?(values: FormValues): FormValues;
validateError?: ValidateValuesFunction;
validateWarning?: ValidateValuesFunction;
validateSuccess?: (values: FormValues, errors: FormErrors) => FormErrors;
asyncValidators?: {
[field: string]: (value: FormValue) => Promise<any>
};
dontPreventDefault?: boolean;
}
export interface FormApi {
setAllValues(values: FormValues, noTouch?: boolean): void;
setValue(field: string, value: any, noTouch?: boolean): void;
getValue(field: string, fallback?: any): any;
setNestedError(field: string, value?: boolean): void;
getError(field: string): FormError;
setTouched(field: string, value?: boolean): void;
getTouched(field: string): boolean;
addValue(field: string, value: any): void;
removeValue(field: string, index: number): void;
swapValues(field: string, index: number, destIndex: number): void;
setAllTouched(dirty?: boolean, state?: Partial<FormState>): void;
resetForm(): void;
submitForm(e?: Pick<React.SyntheticEvent<any>, 'preventDefault'>): void;
// State
values: FormValues;
touched: Touched;
errors: FormErrors;
warnings: FormErrors;
successes: FormErrors;
submits: number;
submitted: boolean;
asyncValidations: number;
validating: {[field: string]: boolean};
validationFailures: number;
validationFailed: {[field: string]: boolean};
// Methods
submitForm(event: React.SyntheticEvent<any>): void;
setValue(fieldName: string, value: any): void;
setAllValues(values: FormValues): void;
setError(field: string, error: string): void;
setWarning(field: string, warning: string): void;
setSuccess(field: string, success: string): void;
setTouched(field: string, touched: boolean): void;
setAllTouched(touches: {[field: string]: boolean}): void;
addValue(name: string, value: any): void;
removeValue(name: string, index: number): void;
swapValues(name: string, index1: number, index2: number): void;
resetAll(): void;
getFormState(): FormState;
setFormState(state: FormState): void;
}
export type ValidateValuesFunction = (values: FormValues) => FormErrors;
export interface FormFunctionProps extends FormProps, FormState, FormApi {}
export interface FormContext {
@@ -65,10 +86,9 @@ export interface FormContext {
export class Form
extends React.Component<
FormProps & { children?: ((props: FormFunctionProps) => RenderReturn) | RenderReturn },
FormState
FormProps & { children?: ((props: FormFunctionProps) => RenderReturn) | RenderReturn }
>
implements FormApi, React.ChildContextProvider<FormContext> {
implements React.ChildContextProvider<FormContext> {
static defaultProps: FormProps;
static childContextTypes: {
formApi: React.Validator<any>
@@ -80,207 +100,87 @@ export class Form
componentWillReceiveProps(nextProps: Readonly<Partial<FormProps>>, nextContext: any): void;
componentWillUmount(): void;
// API
setAllValues(values: FormValues, noTouch?: boolean): void;
setValue(field: string, value: any, noTouch?: boolean): void;
getValue(field: string, fallback?: any): any;
setNestedError(field: string, value?: boolean): void;
getError(field: string): FormError;
setTouched(field: string, value?: boolean): void;
getTouched(field: string): boolean;
addValue(field: string, value: any): void;
removeValue(field: string, index: number): void;
swapValues(field: string, index: number, destIndex: number): void;
setAllTouched(dirty?: boolean, state?: Partial<FormState>): void;
resetForm(): void;
submitForm(e?: Pick<React.SyntheticEvent<any>, 'preventDefault'>): void;
// Utils
getAPI(): FormApi;
setFormState(newState: Partial<FormState>, silent?: boolean): void;
emitChange(state: FormState, initial?: boolean): void;
validate(values: FormValues, state: FormState, props: FormProps): FormErrors;
render(): RenderReturn;
}
export interface FormFieldApi {
setAllValues(values: FormValues, noTouch?: boolean): void;
setValue(value: any, noTouch?: boolean): void;
getValue(fallback?: any): any;
setNestedError(value?: boolean): void;
export const NestedForm: React.StatelessComponent<FieldProps>;
export function FormField(component: React.ComponentType<any>): React.ComponentClass<any>;
// Fields
export interface FieldApi {
getValue(): FormValue;
getError(): FormError;
setTouched(value?: boolean): void;
getWarning(): FormError;
getSuccess(): FormError;
getTouched(): boolean;
addValue(value: any): void;
removeValue(index: number): void;
swapValues(index: number, destIndex: number): void;
setAllTouched(dirty?: boolean, state?: Partial<FormState>): void;
resetForm(): void;
submitForm(e?: Pick<React.SyntheticEvent<any>, 'preventDefault'>): void;
getFieldName(): string;
setValue(value: FormValue): void;
setError(error: FormError): void;
setWarning(warning: FormError): void;
setSuccess(success: FormError): void;
setTouched(touched: boolean): void;
}
export interface FormFieldPropsWithField {
field?: string;
children(api: FormFieldApi): React.ReactElement<any> | null;
}
export interface FormFieldPropsWithoutField {
children(api: FormApi): RenderReturn;
}
export type FormFieldProps = FormFieldPropsWithField | FormFieldPropsWithoutField;
export const FormField: React.SFC<FormFieldProps>;
// FormError
export interface FormErrorProps {
field?: FormFieldPropsWithField['field'];
className?: string;
style?: React.HTMLAttributes<HTMLElement>['style'];
}
export const FormError: React.SFC<FormErrorProps>;
export interface FormInputProps {
field?: FormFieldPropsWithField['field'];
export interface FieldProps {
field?: string | string[] | React.ReactText[] | Array<(string | React.ReactText[])>;
showErrors?: boolean;
errorBefore?: boolean;
isForm?: boolean;
className?: string;
errorProps?: FormErrorProps;
}
export interface FormInputPropsWithChildren extends FormInputProps {
children(api: FormFieldApi): React.ReactElement<any> | null;
}
export const FormInput: React.SFC<FormInputPropsWithChildren>;
export type SelectOptions = Array<{
value: FormValue
label: string
}>;
// ==============================
// Inputs
// ==============================
export interface SelectProps extends FieldProps, React.SelectHTMLAttributes<HTMLSelectElement> {
options: SelectOptions;
}
export type EventHandler<T, E> = (e: E, cb: () => void) => void;
export type ChangeHandler<T> = EventHandler<T, React.ChangeEvent<T>>;
export type FocusHandler<T> = EventHandler<T, React.FocusEvent<T>>;
export type ClickHandler<T> = EventHandler<T, React.MouseEvent<T>>;
export const Select: React.StatelessComponent<SelectProps>;
// Prop interfaces are intermediate interfaces to "redefine" the type of some events
// onChange:React.EventHandler => onChange:any => onChange:CustomEventHandler
export const Text: React.StatelessComponent<FieldProps & React.InputHTMLAttributes<HTMLInputElement>>;
export const TextArea: React.StatelessComponent<FieldProps & React.TextareaHTMLAttributes<HTMLTextAreaElement>>;
export interface SelectOption {
label: string;
value: any;
disabled?: boolean;
}
export interface SelectAttrs extends React.SelectHTMLAttributes<HTMLSelectElement> {
onChange?: any;
onBlur?: any;
}
export interface SelectProps extends SelectAttrs {
options: ReadonlyArray<SelectOption>;
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
onChange?: ChangeHandler<HTMLSelectElement>;
onBlur?: FocusHandler<HTMLSelectElement>;
isForm?: FormInputProps['isForm'];
noTouch?: boolean;
errorProps?: FormInputProps['errorProps'];
placeholder?: string;
}
export const Select: React.SFC<SelectProps>;
export interface InputAttrs extends React.InputHTMLAttributes<HTMLInputElement> {
onChange?: any;
onBlur?: any;
}
export interface CheckboxProps extends InputAttrs {
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
onChange?: ChangeHandler<HTMLInputElement>;
onBlur?: FocusHandler<HTMLInputElement>;
isForm?: FormInputProps['isForm'];
noTouch?: boolean;
errorProps?: FormInputProps['errorProps'];
}
export const Checkbox: React.SFC<CheckboxProps>;
export interface TextareaAttrs extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
onChange?: any;
onBlur?: any;
}
export interface TextareaProps extends TextareaAttrs {
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
onChange?: ChangeHandler<HTMLTextAreaElement>;
onBlur?: FocusHandler<HTMLTextAreaElement>;
isForm?: FormInputProps['isForm'];
noTouch?: boolean;
errorProps?: FormInputProps['errorProps'];
}
export const Textarea: React.SFC<TextareaProps>;
export interface NestedFormProps extends FormProps {
field?: FormInputProps['field'];
children?: React.ReactElement<FormProps> | [React.ReactElement<FormProps>];
errorProps?: FormInputProps['errorProps'];
}
export const NestedForm: React.SFC<NestedFormProps>;
export interface TextProps extends InputAttrs {
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
onChange?: ChangeHandler<HTMLInputElement>;
onBlur?: FocusHandler<HTMLInputElement>;
isForm?: FormInputProps['isForm'];
noTouch?: boolean;
errorProps?: FormInputProps['errorProps'];
}
export const Text: React.SFC<TextProps>;
export interface RadioGroupProps {
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
isForm?: FormInputProps['isForm'];
errorProps?: FormInputProps['errorProps'];
}
export interface RadioGroupContext {
formRadioGroup: RadioGroup;
group: FieldApi;
}
export class RadioGroup extends React.Component<RadioGroupProps> implements FormFieldApi {
static childContextTypes: {
formRadioGroup: React.Validator<any>
export class RadioGroup
extends React.Component<
FieldProps & { children?: ((props: FieldApi) => RenderReturn) | RenderReturn }
>
implements React.ChildContextProvider<RadioGroupContext> {
getChildContext(): {
group: FieldApi;
};
setAllValues: FormFieldApi['setAllValues'];
setValue: FormFieldApi['setValue'];
getValue: FormFieldApi['getValue'];
setNestedError: FormFieldApi['setNestedError'];
getError: FormFieldApi['getError'];
setTouched: FormFieldApi['setTouched'];
getTouched: FormFieldApi['getTouched'];
addValue: FormFieldApi['addValue'];
removeValue: FormFieldApi['removeValue'];
swapValues: FormFieldApi['swapValues'];
setAllTouched: FormFieldApi['setAllTouched'];
resetForm: FormFieldApi['resetForm'];
submitForm: FormFieldApi['submitForm'];
getChildContext(): RadioGroupContext;
}
export interface InputWIthoutClick extends InputAttrs {
onClick?: any;
export const Radio: React.StatelessComponent<FieldProps & React.InputHTMLAttributes<HTMLInputElement> & {group: FieldApi}>;
export const Checkbox: React.StatelessComponent<FieldProps & React.InputHTMLAttributes<HTMLInputElement>>;
// Styled Fields
export interface StyledProps extends FieldProps {
noMessage?: boolean;
messageBefore?: boolean;
touchValidation?: boolean;
}
export interface RadioProps extends InputWIthoutClick {
onClick?: ClickHandler<HTMLInputElement>;
onChange?: ChangeHandler<HTMLInputElement>;
onBlur?: FocusHandler<HTMLInputElement>;
}
export class Radio extends React.Component<RadioProps> {
static contextTypes: {
formRadioGroup: React.Validator<any>
export const StyledCheckbox: React.StatelessComponent<StyledProps & React.InputHTMLAttributes<HTMLInputElement> & {label: string}>;
export const StyledTextArea: React.StatelessComponent<StyledProps & React.TextareaHTMLAttributes<HTMLTextAreaElement>>;
export const StyledSelect: React.StatelessComponent<StyledProps & SelectProps & React.InputHTMLAttributes<HTMLSelectElement>>;
export const StyledText: React.StatelessComponent<StyledProps & React.InputHTMLAttributes<HTMLInputElement>>;
export const StyledRadio: React.StatelessComponent<StyledProps & React.InputHTMLAttributes<HTMLInputElement> & {group: FieldApi, label: string}>;
export class StyledRadioGroup
extends React.Component<
StyledProps & { children?: ((props: FieldApi) => RenderReturn) | RenderReturn }
>
implements React.ChildContextProvider<RadioGroupContext> {
getChildContext(): {
group: FieldApi
};
context: RadioGroupContext;
}

View File

@@ -1,78 +1,672 @@
import * as React from 'react';
import {
Form,
FormError,
FormInput,
// Inputs
Select,
Checkbox,
Textarea,
NestedForm,
Text,
RadioGroup,
Radio
Form,
Text,
TextArea,
Radio,
RadioGroup,
Select,
Checkbox,
NestedForm,
FormValues,
FormErrors,
StyledText,
StyledRadioGroup,
StyledRadio,
StyledTextArea,
StyledCheckbox,
StyledSelect,
FieldApi,
FormField,
FormApi
} from 'react-form';
<Form />;
// Basic Form Example
const statusOptions = [
{
label: 'Single',
value: 'single'
},
{
label: 'In a Relationship',
value: 'relationship'
},
{
label: "It's Complicated",
value: 'complicated'
}
];
<Form>
{() => null}
</Form>;
class BasicForm extends React.Component {
constructor(props: {}) {
super(props);
this.state = {};
}
<Form>
{() => <div/>}
</Form>;
render() {
return (
<div>
<Form onSubmit={submittedValues => this.setState({ submittedValues })}>
{ formApi => (
<form onSubmit={formApi.submitForm} id="form2">
<label htmlFor="firstName">First name</label>
<Text field="firstName" id="firstName" />
<label htmlFor="lastName">Last name</label>
<Text field="lastName" id="lastName" />
<RadioGroup field="gender">
{ group => (
<div>
<label htmlFor="male" className="mr-2">Male</label>
<Radio group={group} value="male" id="male" className="mr-3 d-inline-block" />
<label htmlFor="female" className="mr-2">Female</label>
<Radio group={group} value="female" id="female" className="d-inline-block" />
</div>
)}
</RadioGroup>
<label htmlFor="bio">Bio</label>
<TextArea field="bio" id="bio" />
<label htmlFor="authorize" className="mr-2">Authorize</label>
<Checkbox field="authorize" id="authorize" className="d-inline-block" />
<label htmlFor="status" className="d-block">Relationship status</label>
<Select field="status" id="status" options={statusOptions} />
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
)}
</Form>
</div>
);
}
}
<Form>
{ ({ submitForm }) => <button onClick={submitForm}>Submit</button> }
</Form>;
// Form with Arrays
class FormWithArrays extends React.Component {
constructor(props: {}) {
super(props);
this.state = {};
}
<FormError field="" />;
render() {
return (
<div>
<Form
onSubmit={submittedValues => this.setState({ submittedValues })}>
{ formApi => (
<form onSubmit={formApi.submitForm} id="form3">
<label htmlFor="firstName2">First name</label>
<Text field="firstName" id="firstName2" />
<label htmlFor="friend1">Friend1</label>
<Text field={['friends', 0]} id="friend1" />
<label htmlFor="friend2">Friend2</label>
<Text field={['friends', 1]} id="friend2" />
<label htmlFor="friend3">Friend3</label>
<Text field={['friends', 2]} id="friend3" />
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
)}
</Form>
</div>
);
}
}
const CustomInput: React.SFC<React.HTMLAttributes<HTMLInputElement> & {field?: string}> =
({field, ...rest}) => {
return (
<FormInput field={field}>
{({ setValue, getValue, setTouched }) => {
return (
<input
{...rest}
value={getValue()}
onChange={(e) => setValue(e.target.value)}
onBlur={() => setTouched(true)}
/>
);
}}
</FormInput>
);
// Field Syntax
const Friend = ({ i }: {i: number}) => (
<div>
<h2>Friend</h2>
<label htmlFor={`friend-first-${i}`}>First name</label>
<Text field={['friends', i, 'firstName']} id={`friend-first-${i}`} />
<label htmlFor={`friend-last-${i}`}>Last name</label>
<Text field={[['friends', i], 'lastName']} id={`friend-last-${i}`} />
<label htmlFor={`friend-street-${i}`}>Street</label>
<Text field={['friends', i, 'address', 'street']} id={`friend-street-${i}`} />
<label htmlFor={`friend-zip-${i}`}>Zipcode</label>
<Text field={['friends', i, 'lastName.zip']} id={`friend-zip-${i}`} />
</div>
);
class FormWithSpecialFieldSyntax extends React.Component {
constructor(props: {}) {
super(props);
this.state = {};
}
render() {
return (
<div>
<Form
onSubmit={submittedValues => this.setState({ submittedValues })}>
{ formApi => (
<form onSubmit={formApi.submitForm} id="syntax-form">
<label htmlFor="nickname1">Nickname</label>
<Text field={['nicknames', 0]} id="nickname1" />
<label htmlFor="nickname2">Nickname</label>
<Text field={['nicknames', 1]} id="nickname2" />
<Friend i={0} />
<Friend i={1} />
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
)}
</Form>
</div>
);
}
}
// Nested Form
const Questions = () => (
<NestedForm field="questions">
<Form>
{ formApi => (
<div>
<label htmlFor="color">Whats your favorite color?</label>
<Text field="color" id="color" />
<label htmlFor="food">Whats your favorite food?</label>
<Text field="food" id="food" />
<label htmlFor="car">Whats type of car do you drive?</label>
<Text field="car" id="car" />
</div>
)}
</Form>
</NestedForm>
);
class NestedFormExample extends React.Component {
constructor(props: {}) {
super(props);
this.state = {};
}
render() {
return (
<div>
<Form onSubmit={submittedValues => this.setState({ submittedValues })}>
{ formApi => (
<form onSubmit={formApi.submitForm} id="form4">
<label htmlFor="firstName3">First name</label>
<Text field="firstName" id="firstName3" />
<Questions />
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
)}
</Form>
</div>
);
}
}
// Dynamic Forms
class DynamicForm extends React.Component {
constructor(props: {}) {
super(props);
this.state = {};
}
render() {
return (
<div>
<Form
onSubmit={submittedValues => this.setState({ submittedValues })}>
{ formApi => (
<div>
<button
onClick={() => formApi.addValue('siblings', '')}
type="button"
className="mb-4 mr-4 btn btn-success">Add Sibling</button>
<form onSubmit={formApi.submitForm} id="dynamic-form">
<label htmlFor="dynamic-first">First name</label>
<Text field="firstName" id="dynamic-first" />
{ formApi.values.siblings && formApi.values.siblings.map((sibling: string, i: number) => (
<div key={`sibling${i}`}>
<label htmlFor={`sibling-name-${i}`}>Name</label>
<Text field={['siblings', i]} id={`sibling-name-${i}`} />
<button
onClick={() => formApi.removeValue('siblings', i)}
type="button"
className="mb-4 btn btn-danger">Remove</button>
</div>
))}
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
</div>
)}
</Form>
</div>
);
}
}
// Array of Nested Forms
const MyFriend = ({ i }: {i: number}) => (
<NestedForm field={['friends', i]} key={`nested-friend-${i}`}>
<Form>
{ formApi => (
<div>
<h2>Friend</h2>
<label htmlFor={`nested-friend-first-${i}`}>First name</label>
<Text field="firstName" id={`nested-friend-first-${i}`} />
<label htmlFor={`nested-friend-last-${i}`}>Last name</label>
<Text field="lastName" id={`nested-friend-last-${i}`} />
</div>
)}
</Form>
</NestedForm>
);
class FormWithArrayOfNestedForms extends React.Component {
constructor(props: {}) {
super(props);
this.state = {};
}
render() {
return (
<div>
<Form
onSubmit={submittedValues => this.setState({ submittedValues })}>
{ formApi => (
<div>
<form onSubmit={formApi.submitForm} id="form3">
<MyFriend i={0} />
<MyFriend i={1} />
<MyFriend i={2} />
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
</div>
)}
</Form>
</div>
);
}
}
// Styled Form
class StyledForm extends React.Component {
constructor(props: {}) {
super(props);
this.state = {};
}
errorValidator = (values: FormValues) => {
const validateFirstName = (firstName: string) => {
return !firstName ? 'First name is required.' : undefined;
};
const validateLastName = (lastName: string) => {
return !lastName ? 'Last name is required.' : undefined;
};
const validateGender = (gender: string) => {
return !gender ? 'Gender is required.' : undefined;
};
const validateBio = (bio: string) => {
return !bio ? 'Bio is required.' : undefined;
};
const validateAuthorize = (authorize: boolean) => {
return !authorize ? 'Please check authorize.' : undefined;
};
const validateStatus = (status: string) => {
return !status ? 'Status is required.' : undefined;
};
return {
firstName: validateFirstName(values.firstName),
lastName: validateLastName(values.lastName),
gender: validateGender(values.gender),
bio: validateBio(values.bio),
authorize: validateAuthorize(values.authorize),
status: validateStatus(values.status)
};
}
const events = {
onChange: (e: React.SyntheticEvent<HTMLElement>, cb: () => void): null => null,
onBlur: (e: React.SyntheticEvent<HTMLElement>, cb: () => void): null => null
warningValidator = (values: FormValues) => {
const validateFirstName = (firstName: string) => {
return firstName && firstName.length < 2 ? 'First name must be longer than 2 characters.' : undefined;
};
const validateLastName = (lastName: string) => {
return lastName && lastName.length < 2 ? 'Last name must be longer than 2 characters.' : undefined;
};
const validateBio = (bio: string) => {
return bio && bio.replace(/s+/g, ' ').trim().split(' ').length < 5 ? 'Bio should have more than 5 words.' : undefined;
};
return {
firstName: validateFirstName(values.firstName),
lastName: validateLastName(values.lastName),
gender: undefined,
bio: validateBio(values.bio),
authorize: undefined,
status: undefined
};
}
successValidator = (values: FormValues, errors: FormErrors) => {
const validateFirstName = () => {
return !errors['firstName'] ? 'Nice name!' : undefined;
};
const validateLastName = () => {
return !errors['lastName'] ? 'Your last name is sick!' : undefined;
};
const validateGender = () => {
return !errors['gender'] ? 'Thanks for entering your gender.' : undefined;
};
const validateBio = () => {
return !errors['bio'] ? 'Cool Bio!' : undefined;
};
const validateAuthorize = () => {
return !errors['authorize'] ? 'You are now authorized.' : undefined;
};
const validateStatus = () => {
return !errors['status'] ? 'Thanks for entering your status.' : undefined;
};
return {
firstName: validateFirstName(),
lastName: validateLastName(),
gender: validateGender(),
bio: validateBio(),
authorize: validateAuthorize(),
status: validateStatus()
};
}
render() {
return <Form
validateError={this.errorValidator}
validateWarning={this.warningValidator}
validateSuccess={this.successValidator}
onSubmit={submittedValues => this.setState({ submittedValues })}>
{ formApi => (
<form onSubmit={formApi.submitForm} id="form2">
<label htmlFor="firstName">First name</label>
<StyledText field="firstName" id="firstName" />
<label htmlFor="lastName">Last name</label>
<StyledText field="lastName" id="lastName" />
<label>Choose Gender</label>
<StyledRadioGroup field="gender">
{ group => (
<div>
<StyledRadio group={group} value="male" id="male" label="Male" className="mr-3 d-inline-block" />
<StyledRadio group={group} value="female" id="female" label="Female" className="d-inline-block" />
</div>
)}
</StyledRadioGroup>
<label htmlFor="bio">Bio</label>
<StyledTextArea field="bio" id="bio" />
<StyledCheckbox field="authorize" id="authorize" label="Authorize" className="d-inline-block" />
<label htmlFor="status" className="d-block">Relationship status</label>
<StyledSelect field="status" id="status" options={statusOptions} />
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
)}
</Form>;
}
}
// Define a custom message component
const Message = ({ color, message }: {color: string, message: string}) => {
return (
<div className="mb-4" style={{ color }}>
<small>{message}</small>
</div>
);
};
const onClick = (e: React.SyntheticEvent<HTMLElement>, cb: () => void): null => null;
<Select options={[]} />;
<Select field="" options={[{label: '', value: '', disabled: false}]} {...events} />;
// Define your custom input
// Note, the ...rest is important because it allows you to pass any
// additional fields to the internal <input>.
class CustomTextWrapper extends React.Component<{
fieldApi: FieldApi
onInput: any
}> {
render() {
const {
fieldApi,
onInput,
...rest
} = this.props;
<Checkbox />;
<Checkbox field="" checked={false} {...events} />;
const {
getValue,
getError,
getWarning,
getSuccess,
setValue,
setTouched,
} = fieldApi;
<Textarea />;
<Textarea field="" {...events} />;
const error = getError();
const warning = getWarning();
const success = getSuccess();
<Form>
<NestedForm>
<Form />
</NestedForm>
</Form>;
return (
<div>
<input
value={getValue()}
onInput={(e) => {
setValue(e.currentTarget.value);
if (onInput) {
onInput(e);
}
}}
onBlur={() => {
setTouched(true);
}}
{...rest} />
{ error ? <Message color="red" message={error} /> : null }
{ !error && warning ? <Message color="orange" message={warning} /> : null }
{ !error && !warning && success ? <Message color="green" message={success} /> : null }
</div>
);
}
}
<Text />;
<Text field="" {...events} />;
// Use the form field and your custom input together to create your very own input!
const CustomText = FormField(CustomTextWrapper);
<RadioGroup field="">
<Radio />
<Radio {...events} onClick={onClick} />
</RadioGroup>;
const errorValidator = (values: FormValues) => {
return {
hello: !values.hello || !values.hello.match(/Hello World/) ? "Input must contain 'Hello World'" : undefined
};
};
const warningValidator = (values: FormValues) => {
return {
hello: !values.hello ||
!values.hello.match(/^Hello World$/) ? "Input should equal 'Hello World'" : undefined
};
};
const successValidator = (values: FormValues) => {
return {
hello: values.hello && values.hello.match(/Hello World/) ? "Thanks for entering 'Hello World'!" : undefined
};
};
class FormWithCustomInput extends React.Component {
render() {
return (
<div>
<Form
validateWarning={warningValidator}
validateSuccess={successValidator}
validateError={errorValidator}>
{ formApi => (
<form onSubmit={formApi.submitForm} id="form5">
<label htmlFor="firstName4">First name</label>
<Text field="firstName" id="firstName4" />
<label htmlFor="hello2">Custom hello world</label>
<CustomText field="hello" id="hello2" />
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
)}
</Form>
</div>
);
}
}
// Async Validators
const aserrorValidator = (values: FormValues) => {
return {
username: !values.username || values.username.trim() === '' ? 'Username is a required field' : undefined
};
};
const assuccessValidator = (values: FormValues, errors: FormErrors) => {
return {
username: !errors.username ? 'Awesome! your username is good to go!' : undefined
};
};
const doesUsernameExist = (username: string) => new Promise((resolve, reject) => setTimeout(() => {
// Simulate username check
if (['joe', 'tanner', 'billy', 'bob'].indexOf(username)) {
resolve({ error: 'That username is taken', success: null });
}
// Simulate request faulure
if (username === 'reject') {
reject('Failure while making call to validate username does not exist');
}
// Sumulate username success check
resolve({});
}, 2000));
const asyncValidators = {
username: async (username: string) => {
const validations = await doesUsernameExist(username);
return validations;
}
};
class AsynchronousFormValidation extends React.Component {
render() {
return (
<div>
<Form
validateError={aserrorValidator}
validateSuccess={assuccessValidator}
asyncValidators={asyncValidators}>
{ formApi => (
<form onSubmit={formApi.submitForm} id="form6">
<label htmlFor="username">Username</label>
<Text field="username" id="username" />
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
)}
</Form>
</div>
);
}
}
// Nested Async Validators
const NestedNestedFormContent = ({ formApi }: {formApi: FormApi}) => {
return (
<div>
<label htmlFor="username4">Nested Username</label>
<Text field="username" id="username4" />
</div>
);
};
const NestedFormContent = ({ formApi }: {formApi: FormApi}) => {
return (
<div>
<label htmlFor="username3">Nested Username</label>
<Text field="username" id="username3" />
<NestedForm field="nestednested">
<Form
validateError={errorValidator}
validateSuccess={successValidator}
asyncValidators={asyncValidators3}>
{
formApi =>
<NestedNestedFormContent formApi={formApi} />
}
</Form>
</NestedForm>
</div>
);
};
const FormContent = ({ formApi }: {formApi: FormApi}) => {
return (
<div>
<form onSubmit={formApi.submitForm} id="form7">
<label htmlFor="username2">Username</label>
<Text field="username" id="username2" />
<NestedForm field="nested">
<Form
validateError={errorValidator}
validateSuccess={successValidator}
asyncValidators={asyncValidators2}>
{
formApi =>
<NestedFormContent formApi={formApi} />
}
</Form>
</NestedForm>
<button type="submit" className="mb-4 btn btn-primary">Submit</button>
</form>
</div>
);
};
const naserrorValidator = (values: FormValues) => {
return {
username: !values.username || values.username.trim() === '' ? 'Username is a required field' : undefined
};
};
const nassuccessValidator = (values: FormValues, errors: FormErrors) => {
return {
username: !errors.username ? 'Awesome! your username is good to go!' : undefined
};
};
const nasdoesUsernameExist = (username: string, ms: number) => new Promise((resolve, reject) => setTimeout(() => {
// Simulate username check
if (['joe', 'tanner', 'billy', 'bob'].indexOf(username)) {
resolve({ error: 'That username is taken', success: null });
}
// Simulate request faulure
if (username === 'reject') {
reject('Failure while making call to validate username does not exist');
}
// Sumulate username success check
resolve({});
}, ms));
const nasasyncValidators = {
username: async (username: string) => {
const validations = await nasdoesUsernameExist(username, 2000);
return validations;
}
};
const asyncValidators2 = {
username: async (username: string) => {
const validations = await nasdoesUsernameExist(username, 4000);
return validations;
}
};
const asyncValidators3 = {
username: async (username: string) => {
const validations = await nasdoesUsernameExist(username, 6000);
return validations;
}
};
class NestedAsynchronousFormValidation extends React.Component {
render() {
return (
<div>
<Form
validateError={naserrorValidator}
validateSuccess={nassuccessValidator}
asyncValidators={nasasyncValidators}>
{
formApi =>
<FormContent formApi={formApi} />
}
</Form>
</div>
);
}
}

286
types/react-form/v1/index.d.ts vendored Normal file
View File

@@ -0,0 +1,286 @@
// Type definitions for react-form 1.3
// Project: https://github.com/tannerlinsley/react-form#readme
// Definitions by: Cameron McAteer <https://github.com/cameron-mcateer>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
import * as React from 'react';
export type FormValue = any;
export type FormError = string | undefined;
export interface Nested<T> {
[key: string]: T | Nested<T>;
}
export type FormValues = Nested<FormValue>;
export type Touched = Nested<boolean>;
export type FormErrors = {[key: string]: FormError} | [{[key: string]: FormError}];
export type NestedErrors = Nested<FormErrors>;
export type RenderReturn = JSX.Element | false | null;
export interface FormProps {
loadState?(props: FormProps, self: Form): FormState | undefined;
defaultValues?: FormValues;
preValidate?(values: FormValues, state: FormState, props: FormProps, self: Form): FormValues;
validate?(values: FormValues, state: FormState, props: FormProps): FormErrors;
onValidationFail?(values: FormValues, state: FormState, props: FormProps, self: Form): void;
onChange?(state: FormState, props: FormProps, initial: boolean | FormProps, self: Form): void;
saveState?(state: FormState, props: FormProps, self: Form): void;
willUnmount?(state: FormState, props: FormProps, self: Form): void;
preSubmit?(values: FormValues, state: FormState, props: FormProps, self: Form): FormValues;
onSubmit?(values: FormValues, state: FormState, props: FormProps, self: Form): void;
postSubmit?(values: FormValues, state: FormState, props: FormProps, self: Form): void;
}
export interface FormState {
values: FormValues;
touched: Touched;
errors: FormErrors;
nestedErrors: NestedErrors;
dirty?: boolean;
}
export const FormDefaultProps: FormProps;
export interface FormApi {
setAllValues(values: FormValues, noTouch?: boolean): void;
setValue(field: string, value: any, noTouch?: boolean): void;
getValue(field: string, fallback?: any): any;
setNestedError(field: string, value?: boolean): void;
getError(field: string): FormError;
setTouched(field: string, value?: boolean): void;
getTouched(field: string): boolean;
addValue(field: string, value: any): void;
removeValue(field: string, index: number): void;
swapValues(field: string, index: number, destIndex: number): void;
setAllTouched(dirty?: boolean, state?: Partial<FormState>): void;
resetForm(): void;
submitForm(e?: Pick<React.SyntheticEvent<any>, 'preventDefault'>): void;
}
export interface FormFunctionProps extends FormProps, FormState, FormApi {}
export interface FormContext {
formApi: FormApi;
}
export class Form
extends React.Component<
FormProps & { children?: ((props: FormFunctionProps) => RenderReturn) | RenderReturn },
FormState
>
implements FormApi, React.ChildContextProvider<FormContext> {
static defaultProps: FormProps;
static childContextTypes: {
formApi: React.Validator<any>
};
getDefaultState(): FormState;
getChildContext(): FormContext;
componentWillMount(): void;
componentWillReceiveProps(nextProps: Readonly<Partial<FormProps>>, nextContext: any): void;
componentWillUmount(): void;
// API
setAllValues(values: FormValues, noTouch?: boolean): void;
setValue(field: string, value: any, noTouch?: boolean): void;
getValue(field: string, fallback?: any): any;
setNestedError(field: string, value?: boolean): void;
getError(field: string): FormError;
setTouched(field: string, value?: boolean): void;
getTouched(field: string): boolean;
addValue(field: string, value: any): void;
removeValue(field: string, index: number): void;
swapValues(field: string, index: number, destIndex: number): void;
setAllTouched(dirty?: boolean, state?: Partial<FormState>): void;
resetForm(): void;
submitForm(e?: Pick<React.SyntheticEvent<any>, 'preventDefault'>): void;
// Utils
getAPI(): FormApi;
setFormState(newState: Partial<FormState>, silent?: boolean): void;
emitChange(state: FormState, initial?: boolean): void;
validate(values: FormValues, state: FormState, props: FormProps): FormErrors;
render(): RenderReturn;
}
export interface FormFieldApi {
setAllValues(values: FormValues, noTouch?: boolean): void;
setValue(value: any, noTouch?: boolean): void;
getValue(fallback?: any): any;
setNestedError(value?: boolean): void;
getError(): FormError;
setTouched(value?: boolean): void;
getTouched(): boolean;
addValue(value: any): void;
removeValue(index: number): void;
swapValues(index: number, destIndex: number): void;
setAllTouched(dirty?: boolean, state?: Partial<FormState>): void;
resetForm(): void;
submitForm(e?: Pick<React.SyntheticEvent<any>, 'preventDefault'>): void;
}
export interface FormFieldPropsWithField {
field?: string;
children(api: FormFieldApi): React.ReactElement<any> | null;
}
export interface FormFieldPropsWithoutField {
children(api: FormApi): RenderReturn;
}
export type FormFieldProps = FormFieldPropsWithField | FormFieldPropsWithoutField;
export const FormField: React.SFC<FormFieldProps>;
// FormError
export interface FormErrorProps {
field?: FormFieldPropsWithField['field'];
className?: string;
style?: React.HTMLAttributes<HTMLElement>['style'];
}
export const FormError: React.SFC<FormErrorProps>;
export interface FormInputProps {
field?: FormFieldPropsWithField['field'];
showErrors?: boolean;
errorBefore?: boolean;
isForm?: boolean;
className?: string;
errorProps?: FormErrorProps;
}
export interface FormInputPropsWithChildren extends FormInputProps {
children(api: FormFieldApi): React.ReactElement<any> | null;
}
export const FormInput: React.SFC<FormInputPropsWithChildren>;
// ==============================
// Inputs
// ==============================
export type EventHandler<T, E> = (e: E, cb: () => void) => void;
export type ChangeHandler<T> = EventHandler<T, React.ChangeEvent<T>>;
export type FocusHandler<T> = EventHandler<T, React.FocusEvent<T>>;
export type ClickHandler<T> = EventHandler<T, React.MouseEvent<T>>;
// Prop interfaces are intermediate interfaces to "redefine" the type of some events
// onChange:React.EventHandler => onChange:any => onChange:CustomEventHandler
export interface SelectOption {
label: string;
value: any;
disabled?: boolean;
}
export interface SelectAttrs extends React.SelectHTMLAttributes<HTMLSelectElement> {
onChange?: any;
onBlur?: any;
}
export interface SelectProps extends SelectAttrs {
options: ReadonlyArray<SelectOption>;
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
onChange?: ChangeHandler<HTMLSelectElement>;
onBlur?: FocusHandler<HTMLSelectElement>;
isForm?: FormInputProps['isForm'];
noTouch?: boolean;
errorProps?: FormInputProps['errorProps'];
placeholder?: string;
}
export const Select: React.SFC<SelectProps>;
export interface InputAttrs extends React.InputHTMLAttributes<HTMLInputElement> {
onChange?: any;
onBlur?: any;
}
export interface CheckboxProps extends InputAttrs {
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
onChange?: ChangeHandler<HTMLInputElement>;
onBlur?: FocusHandler<HTMLInputElement>;
isForm?: FormInputProps['isForm'];
noTouch?: boolean;
errorProps?: FormInputProps['errorProps'];
}
export const Checkbox: React.SFC<CheckboxProps>;
export interface TextareaAttrs extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
onChange?: any;
onBlur?: any;
}
export interface TextareaProps extends TextareaAttrs {
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
onChange?: ChangeHandler<HTMLTextAreaElement>;
onBlur?: FocusHandler<HTMLTextAreaElement>;
isForm?: FormInputProps['isForm'];
noTouch?: boolean;
errorProps?: FormInputProps['errorProps'];
}
export const Textarea: React.SFC<TextareaProps>;
export interface NestedFormProps extends FormProps {
field?: FormInputProps['field'];
children?: React.ReactElement<FormProps> | [React.ReactElement<FormProps>];
errorProps?: FormInputProps['errorProps'];
}
export const NestedForm: React.SFC<NestedFormProps>;
export interface TextProps extends InputAttrs {
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
onChange?: ChangeHandler<HTMLInputElement>;
onBlur?: FocusHandler<HTMLInputElement>;
isForm?: FormInputProps['isForm'];
noTouch?: boolean;
errorProps?: FormInputProps['errorProps'];
}
export const Text: React.SFC<TextProps>;
export interface RadioGroupProps {
field?: FormInputProps['field'];
showErrors?: FormInputProps['showErrors'];
errorBefore?: FormInputProps['errorBefore'];
isForm?: FormInputProps['isForm'];
errorProps?: FormInputProps['errorProps'];
}
export interface RadioGroupContext {
formRadioGroup: RadioGroup;
}
export class RadioGroup extends React.Component<RadioGroupProps> implements FormFieldApi {
static childContextTypes: {
formRadioGroup: React.Validator<any>
};
setAllValues: FormFieldApi['setAllValues'];
setValue: FormFieldApi['setValue'];
getValue: FormFieldApi['getValue'];
setNestedError: FormFieldApi['setNestedError'];
getError: FormFieldApi['getError'];
setTouched: FormFieldApi['setTouched'];
getTouched: FormFieldApi['getTouched'];
addValue: FormFieldApi['addValue'];
removeValue: FormFieldApi['removeValue'];
swapValues: FormFieldApi['swapValues'];
setAllTouched: FormFieldApi['setAllTouched'];
resetForm: FormFieldApi['resetForm'];
submitForm: FormFieldApi['submitForm'];
getChildContext(): RadioGroupContext;
}
export interface InputWIthoutClick extends InputAttrs {
onClick?: any;
}
export interface RadioProps extends InputWIthoutClick {
onClick?: ClickHandler<HTMLInputElement>;
onChange?: ChangeHandler<HTMLInputElement>;
onBlur?: FocusHandler<HTMLInputElement>;
}
export class Radio extends React.Component<RadioProps> {
static contextTypes: {
formRadioGroup: React.Validator<any>
};
context: RadioGroupContext;
}

View File

@@ -0,0 +1,78 @@
import * as React from 'react';
import {
Form,
FormError,
FormInput,
// Inputs
Select,
Checkbox,
Textarea,
NestedForm,
Text,
RadioGroup,
Radio
} from 'react-form';
<Form />;
<Form>
{() => null}
</Form>;
<Form>
{() => <div/>}
</Form>;
<Form>
{ ({ submitForm }) => <button onClick={submitForm}>Submit</button> }
</Form>;
<FormError field="" />;
const CustomInput: React.SFC<React.HTMLAttributes<HTMLInputElement> & {field?: string}> =
({field, ...rest}) => {
return (
<FormInput field={field}>
{({ setValue, getValue, setTouched }) => {
return (
<input
{...rest}
value={getValue()}
onChange={(e) => setValue(e.target.value)}
onBlur={() => setTouched(true)}
/>
);
}}
</FormInput>
);
};
const events = {
onChange: (e: React.SyntheticEvent<HTMLElement>, cb: () => void): null => null,
onBlur: (e: React.SyntheticEvent<HTMLElement>, cb: () => void): null => null
};
const onClick = (e: React.SyntheticEvent<HTMLElement>, cb: () => void): null => null;
<Select options={[]} />;
<Select field="" options={[{label: '', value: '', disabled: false}]} {...events} />;
<Checkbox />;
<Checkbox field="" checked={false} {...events} />;
<Textarea />;
<Textarea field="" {...events} />;
<Form>
<NestedForm>
<Form />
</NestedForm>
</Form>;
<Text />;
<Text field="" {...events} />;
<RadioGroup field="">
<Radio />
<Radio {...events} onClick={onClick} />
</RadioGroup>;

View File

@@ -0,0 +1,30 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6",
"dom"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": "../../",
"jsx": "react",
"typeRoots": [
"../../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"react-form": [
"react-form/v1"
]
}
},
"files": [
"index.d.ts",
"react-form-tests.tsx"
]
}

View File

@@ -0,0 +1 @@
{ "extends": "dtslint/dt.json" }