Add type definitions for redux-form

This commit is contained in:
Daniel Lytkin
2015-12-21 22:18:21 +06:00
parent 40c60850ad
commit 238f865081
2 changed files with 794 additions and 0 deletions

View File

@@ -0,0 +1,332 @@
/// <reference path="redux-form.d.ts" />
import * as React from 'react';
import {Component, PropTypes} from 'react';
import {createStore, combineReducers} from 'redux';
import {reduxForm, reducer as reduxFormReducer, ReduxFormProps} from 'redux-form';
module SimpleForm {
export const fields = ['firstName', 'lastName', 'email', 'sex', 'favoriteColor', 'employed', 'notes'];
class SimpleForm extends Component<ReduxFormProps, void> {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
};
render() {
const {
fields: {firstName, lastName, email, sex, favoriteColor, employed, notes},
handleSubmit,
resetForm,
submitting
} = this.props;
return (<form onSubmit={handleSubmit}>
<div>
<label>First Name</label>
<div>
<input type="text" placeholder="First Name" {...firstName}/>
</div>
</div>
<div>
<label>Last Name</label>
<div>
<input type="text" placeholder="Last Name" {...lastName}/>
</div>
</div>
<div>
<label>Email</label>
<div>
<input type="email" placeholder="Email" {...email}/>
</div>
</div>
<div>
<label>Sex</label>
<div>
<label>
<input type="radio" {...sex} value="male" checked={sex.value === 'male'}/> Male
</label>
<label>
<input type="radio" {...sex} value="female" checked={sex.value === 'female'}/> Female
</label>
</div>
</div>
<div>
<label>Favorite Color</label>
<div>
<select {...favoriteColor}>
<option></option>
<option value="ff0000">Red</option>
<option value="00ff00">Green</option>
<option value="0000ff">Blue</option>
</select>
</div>
</div>
<div>
<label>
<input type="checkbox" {...employed}/> Employed
</label>
</div>
<div>
<label>Notes</label>
<div>
<textarea
{...notes}
value={notes.value || ''}
/>
</div>
</div>
<div>
<button disabled={submitting} onClick={handleSubmit}>
{submitting ? <i/> : <i/>} Submit
</button>
<button disabled={submitting} onClick={resetForm}>
Clear Values
</button>
</div>
</form>
);
}
}
const Connected = reduxForm({
form: 'simple',
fields
})(SimpleForm);
}
module SynchronousValidation {
export const fields = ['username', 'email', 'age'];
const validate = (values:any) => {
const errors:any = {};
if (!values.username) {
errors.username = 'Required';
} else if (values.username.length > 15) {
errors.username = 'Must be 15 characters or less';
}
if (!values.email) {
errors.email = 'Required';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
if (!values.age) {
errors.age = 'Required';
} else if (isNaN(Number(values.age))) {
errors.age = 'Must be a number';
} else if (Number(values.age) < 18) {
errors.age = 'Sorry, you must be at least 18 years old';
}
return errors;
};
class SynchronousValidationForm extends Component<ReduxFormProps, any> {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
};
render() {
const {fields: {username, email, age}, resetForm, handleSubmit, submitting} = this.props;
return (<form onSubmit={handleSubmit}>
<div>
<label>Username</label>
<div>
<input type="text" placeholder="Username" {...username}/>
</div>
{username.touched && username.error && <div>{username.error}</div>}
</div>
<div>
<label>Email</label>
<div>
<input type="text" placeholder="Email" {...email}/>
</div>
{email.touched && email.error && <div>{email.error}</div>}
</div>
<div>
<label>Age</label>
<div>
<input type="text" placeholder="Age" {...age}/>
</div>
{age.touched && age.error && <div>{age.error}</div>}
</div>
<div>
<button disabled={submitting} onClick={handleSubmit}>
{submitting ? <i/> : <i/>} Submit
</button>
<button disabled={submitting} onClick={resetForm}>
Clear Values
</button>
</div>
</form>
);
}
}
export const Connected = reduxForm({
form: 'synchronousValidation',
fields,
validate
})(SynchronousValidationForm);
}
module SumbitValidation {
export const fields = ['username', 'password'];
const submit = (values:any, dispatch:any) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (['john', 'paul', 'george', 'ringo'].indexOf(values.username) === -1) {
reject({username: 'User does not exist', _error: 'Login failed!'});
} else if (values.password !== 'redux-form') {
reject({password: 'Wrong password', _error: 'Login failed!'});
} else {
resolve();
}
}, 1000); // simulate server latency
});
};
class SubmitValidationForm extends Component<ReduxFormProps, any> {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
error: PropTypes.string,
resetForm: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
};
render() {
const {fields: {username, password}, error, resetForm, handleSubmit, submitting} = this.props;
return (<form onSubmit={handleSubmit(submit)}>
<div>
<label>Username</label>
<div>
<input type="text" placeholder="Username" {...username}/>
</div>
{username.touched && username.error && <div>{username.error}</div>}
</div>
<div>
<label>Password</label>
<div>
<input type="password" placeholder="Password" {...password}/>
</div>
{password.touched && password.error && <div>{password.error}</div>}
</div>
{error && <div>{error}</div>}
<div>
<button disabled={submitting} onClick={handleSubmit}>
{submitting ? <i/> : <i/>} Log In
</button>
<button disabled={submitting} onClick={resetForm}>
Clear Values
</button>
</div>
</form>
);
}
}
export const Connected = reduxForm({
form: 'submitValidation',
fields
})(SubmitValidationForm);
}
module InitializingFromState {
const LOAD = 'redux-form-examples/account/LOAD';
const loadAccount = (data:any) => ({type: LOAD, data});
export const fields = ['firstName', 'lastName', 'age', 'bio'];
const data = { // used to populate "account" reducer when "Load" is clicked
firstName: 'John',
lastName: 'Doe',
age: '42',
bio: 'Born to write amazing Redux code.'
};
interface Props extends ReduxFormProps {
load: Function;
}
class InitializingFromStateForm extends Component<Props, any> {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
load: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
};
render() {
const {fields: {firstName, lastName, age, bio}, handleSubmit, load, submitting} = this.props;
return (
<div>
<div>
<button onClick={() => load(data)}>Load Account</button>
</div>
<form onSubmit={handleSubmit}>
<div>
<label>First Name</label>
<div>
<input type="text" placeholder="First Name" {...firstName}/>
</div>
</div>
<div>
<label>Last Name</label>
<div>
<input type="text" placeholder="Last Name" {...lastName}/>
</div>
</div>
<div>
<label>Age</label>
<div>
<input type="number" placeholder="Age" {...age}/>
</div>
</div>
<div>
<label>Occupation</label>
<div>
<textarea placeholder="Biography" {...bio}/>
</div>
</div>
<div>
<button disabled={submitting} onClick={handleSubmit}>
{submitting ? <i/> : <i/>} Submit
</button>
</div>
</form>
</div>
);
}
}
export const Connected = reduxForm({
form: 'initializing',
fields
},
(state:any) => ({ // mapStateToProps
initialValues: state.account.data // will pull state into form's initialValues
}),
{load: loadAccount} // mapDispatchToProps (will bind action creator to dispatch)
)(InitializingFromStateForm);
}
module NormalizingFormData {
const reducer = combineReducers({
// other reducers
form: reduxFormReducer.normalize({
normalizing: { // <--- name of the form
upper: value => value && value.toUpperCase(), // normalizer for 'upper' field
}
})
});
const store = createStore(reducer);
}

462
redux-form/redux-form.d.ts vendored Normal file
View File

@@ -0,0 +1,462 @@
// Type definitions for redux-form v4.0.3
// Project: https://github.com/erikras/redux-form
// Definitions by: Daniel Lytkin <https://github.com/aikoven>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference path="../es6-promise/es6-promise.d.ts"/>
/// <reference path="../react/react.d.ts" />
/// <reference path="../redux/redux.d.ts" />
declare module 'redux-form' {
import { Component, SyntheticEvent, FormEventHandler } from 'react';
import { Dispatch, ActionCreator, Reducer } from 'redux';
export type FieldValue = any;
export type FormData = {[fieldName:string]: FieldValue};
export interface FieldProp {
/**
* true if this field currently has focus. It will only work if you are
* passing onFocus to your input element.
*/
active: boolean;
/**
* An alias for value only when value is a boolean. Provided for
* convenience of destructuring the whole field object into the props of a
* form element.
*/
checked?: boolean;
/**
* true if the field value has changed from its initialized value.
* Opposite of pristine.
*/
dirty: boolean;
/**
* The error for this field if its value is not passing validation. Both
* synchronous and asynchronous validation errors will be reported here.
*/
error?: string;
/**
* The value for this field as supplied in initialValues to the form.
*/
initialValue: FieldValue;
/**
* true if the field value fails validation (has a validation error).
* Opposite of valid.
*/
invalid: boolean;
/**
* The name of the field. It will be the same as the key in the fields
* Object, but useful if bundling up a field to send down to a specialized
* input component.
*/
name: string;
/**
* A function to call when the form field loses focus. It expects to
* either receive the React SyntheticEvent or the current value of the
* field.
*/
onBlur(eventOrValue:SyntheticEvent|FieldValue):void;
/**
* A function to call when the form field is changed. It expects to either
* receive the React SyntheticEvent or the new value of the field.
* @param eventOrValue
*/
onChange(eventOrValue:SyntheticEvent|FieldValue):void;
/**
* A function to call when the form field receives a 'dragStart' event.
* Saves the field value in the event for giving the field it is dropped
* into.
*/
onDragStart():void;
/**
* A function to call when the form field receives a drop event.
*/
onDrop():void;
/**
* A function to call when the form field receives focus.
*/
onFocus():void;
/**
* An alias for onChange. Provided for convenience of destructuring the
* whole field object into the props of a form element. Added to provide
* out-of-the-box support for Belle components' onUpdate API.
*/
onUpdate():void;
/**
* true if the field value is the same as its initialized value. Opposite
* of dirty.
*/
pristine: boolean;
/**
* true if the field has been touched. By default this will be set when
* the field is blurred.
*/
touched: boolean;
/**
* true if the field value passes validation (has no validation errors).
* Opposite of invalid.
*/
valid: boolean;
/**
* The value of this form field. It will be a boolean for checkboxes, and
* a string for all other input types.
*/
value: FieldValue;
/**
* true if this field has ever had focus. It will only work if you are
* passing onFocus to your input element.
*/
visited: boolean;
}
export interface ReduxFormProps {
/**
* The name of the currently active (with focus) field.
*/
active?: string;
/**
* A function that may be called to initiate asynchronous validation if
* asynchronous validation is enabled.
*/
asyncValidate?: Function;
/**
* true if the asynchronous validation function has been called but has not
* yet returned.
*/
asyncValidating?: boolean;
/**
* Destroys the form state in the Redux store. By default, this will be
* called for you in componentWillUnmount().
*/
destroyForm?():void;
/**
* true if the form data has changed from its initialized values. Opposite
* of pristine.
*/
dirty?: boolean;
/**
* A generic error for the entire form given by the _error key in the
* result from the synchronous validation function, the asynchronous
* validation, or the rejected promise from onSubmit.
*/
error?: string;
/**
* The form data, in the form { field1: <Object>, field2: <Object> }. The
* field objects are meant to be destructured into your input component as
* props, e.g. <input type="text" {...field.name}/>. Each field Object has
* the following properties:
*/
fields?: {[field:string]: FieldProp};
/**
* A function meant to be passed to <form onSubmit={handleSubmit}> or to
* <button onClick={handleSubmit}>. It will run validation, both sync and
* async, and, if the form is valid, it will call
* this.props.onSubmit(data) with the contents of the form data.
* Optionally, you may also pass your onSubmit function to handleSubmit
* which will take the place of the onSubmit prop. For example: <form
* onSubmit={handleSubmit(this.save.bind(this))}> If your onSubmit
* function returns a promise, the submitting property will be set to true
* until the promise has been resolved or rejected. If it is rejected with
* an object matching { field1: 'error', field2: 'error' } then the
* submission errors will be added to each field (to the error prop) just
* like async validation errors are. If there is an error that is not
* specific to any field, but applicable to the entire form, you may pass
* that as if it were the error for a field called _error, and it will be
* given as the error prop.
*/
handleSubmit?(event:SyntheticEvent):void;
handleSubmit?(submit:(data:FormData, dispatch?:Dispatch)
=> Promise<any>|void):FormEventHandler;
/**
* Initializes the form data to the given values. All dirty and pristine
* state will be determined by comparing the current data with these
* initialized values.
* @param data
*/
initializeForm?(data:FormData):void;
/**
* true if the form has validation errors. Opposite of valid.
*/
invalid?: boolean;
/**
* true if the form data is the same as its initialized values. Opposite
* of dirty.
*/
pristine?: boolean;
/**
* Resets all the values in the form to the initialized state, making it
* pristine again.
*/
resetForm?():void;
/**
* The same formKey prop that was passed in. See Editing Multiple Records.
*/
formKey?: string;
/**
* Whether or not your form is currently submitting. This prop will only
* work if you have passed an onSubmit function that returns a promise. It
* will be true until the promise is resolved or rejected.
*/
submitting?: boolean;
/**
* Starts as false. If onSubmit is called, and fails to submit for any
* reason, submitFailed will be set to true. A subsequent successful
* submit will set it back to false.
*/
submitFailed?: boolean;
/**
* Marks the given fields as "touched" to show errors.
* @param field
*/
touch?(...field:string[]):void;
/**
* Marks all fields as "touched" to show errors. This will automatically
* happen on form submission.
*/
touchAll?():void;
/**
* Clears the "touched" flag for the given fields
* @param field
*/
untouch?(...field:string[]):void;
/**
* Clears the "touched" flag for the all fields
*/
untouchAll?():void;
/**
* true if the form passes validation (has no validation errors). Opposite
* of invalid.
*/
valid?: boolean;
/**
* All of your values in the form { field1: <string>, field2: <string> }.
*/
values?: FormData;
}
class ElementClass extends Component<any, any> {
}
interface ClassDecorator {
<T extends (typeof ElementClass)>(component:T): T;
}
interface MapStateToProps {
(state:any, ownProps?:any): any;
}
interface MapDispatchToPropsFunction {
(dispatch:Dispatch, ownProps?:any): any;
}
interface MapDispatchToPropsObject {
[name: string]: ActionCreator;
}
export function reduxForm(config:ReduxFormConfig,
mapStateToProps?:MapStateToProps,
mapDispatchToProps?:MapDispatchToPropsFunction|MapDispatchToPropsObject):ClassDecorator;
export interface ReduxFormConfig {
/**
* a list of all your fields in your form. You may change these dynamically
* at runtime.
*/
fields: string[];
/**
* the name of your form and the key to where your form's state will be
* mounted under the redux-form reducer
*/
form: string;
/**
* field names for which onBlur should trigger a call to the asyncValidate
* function. Defaults to [].
*
* See Asynchronous Blur Validation Example for more details.
*/
asyncBlurFields?: string[];
/**
* a function that takes all the form values, the dispatch function, and
* the props given to your component, and returns a Promise that will
* resolve if the validation is passed, or will reject with an object of
* validation errors in the form { field1: <String>, field2: <String> }.
*
* See Asynchronous Blur Validation Example for more details.
*/
asyncValidate?(values:Object, dispatch:Dispatch, props:Object):
Promise<any>;
/**
* Whether or not to automatically destroy your form's state in the Redux
* store when your component is unmounted. Defaults to true.
*/
destroyOnUnmount?: boolean;
/**
* The key for your sub-form.
*
* See Multirecord Example for more details.
*/
formKey?: string;
/**
* A function that takes the entire Redux state and the reduxMountPoint
* (which defaults to "form"). It defaults to:
* (state, reduxMountPoint) => state[reduxMountPoint].
* The only reason you should provide this is if you are keeping your Redux
* state as something other than plain javascript objects, e.g. an
* Immutable.Map.
*/
getFormState?(state:any, reduxMountPoint:string): any;
/**
* The values with which to initialize your form in componentWillMount().
* Particularly useful when Editing Multiple Records, but can also be used
* with single-record forms. The values should be in the form
* { field1: 'value1', field2: 'value2' }.
*/
initialValues?: {[field:string]: FieldValue};
/**
* The function to call with the form data when the handleSubmit() is fired
* from within the form component. If you do not specify it as a prop here,
* you must pass it as a parameter to handleSubmit() inside your form
* component.
*/
onSubmit?: Function;
/**
* If specified, all the props normally passed into your decorated
* component directly will be passed under the key specified. Useful if
* using other decorator libraries on the same component to avoid prop
* namespace collisions.
*/
propNamespace?: string;
/**
* if true, the decorated component will not be passed any of the onX
* functions as props that will allow it to mutate the state. Useful for
* decorating another component that is not your form, but that needs to
* know about the state of your form.
*/
readonly?: boolean;
/**
* The use of this property is highly discouraged, but if you absolutely
* need to mount your redux-form reducer at somewhere other than form in
* your Redux state, you will need to specify the key you mounted it under
* with this property. Defaults to 'form'.
*
* See Alternate Mount Point Example for more details.
*/
reduxMountPoint?: string;
/**
* If set to true, a failed submit will return a rejected promise. Defaults
* to false. Only use this if you need to detect submit failures and run
* some code when a submit fails.
*/
returnRejectedSubmitPromise?: boolean;
/**
* marks fields as touched when the blur action is fired. Defaults to true.
*/
touchOnBlur?: boolean;
/**
* marks fields as touched when the change action is fired. Defaults to
* false.
*/
touchOnChange?: boolean;
/**
* a synchronous validation function that takes the form values and props
* passed into your component. If validation passes, it should return {}.
* If validation fails, it should return the validation errors in the form
* { field1: <String>, field2: <String> }.
* Defaults to (values, props) => ({}).
*/
validate?(values:FormData, props:{[fieldName:string]: FieldProp}):Object;
}
/**
* @param value The current value of the field.
* @param previousValue The previous value of the field before the current
* action was dispatched.
* @param allValues All the values of the current form.
* @param previousAllValues All the values of the form before the current
* change. Useful to change one field based on a change in another.
*/
export type Normalizer =
(value:FieldValue, previousValue:FieldValue,
allValues:FormData, previousAllValues:FormData) => any;
export const reducer:{
(state:any, action:any): any;
/**
* Returns a form reducer that will also pass each form value through the
* normalizing functions provided. The parameter is an object mapping from
* formName to an object mapping from fieldName to a normalizer function.
* The normalizer function is given four parameters and expected to return
* the normalized value of the field.
*/
normalize(normalizers:{
[formName:string]: {
[fieldName:string]: Normalizer
}
}):Reducer;
/**
* Returns a form reducer that will also pass each action through
* additional reducers specified. The parameter should be an object mapping
* from formName to a (state, action) => nextState reducer. The state
* passed to each reducer will only be the slice that pertains to that
* form.
*/
plugin(reducers:{[formName:string]: Reducer}):Reducer;
}
}