EFX-Forms
Effector JS forms
There are some breaking changes starting from v2
Installation
$ npm install efx-forms
Peer dependencies - library depends on:
react effector effector-react lodash
mjs build included
Main Components
Form / Field
import { Form, Field } from 'efx-forms';
import { FormDataProvider } from 'efx-forms/FormDataProvider';
import { required, email } from 'efx-forms/validators';
const Input = ({ id, label, error, errors, value, ...props }) => (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} value={value || ''} type="text" {...props} />
<span>{error}</span>
</div>
)
const TextField = (props) => <Field Field={Input} {...props} />
const validators = {
name: [required()],
}
const Page = () => {
const submit = (values) => {
console.log(values);
}
return (
<Form name="user-form" onSubmit={submit} validators={validators}>
<TextField name="name" label="Name" />
<TextField
name="email"
label="Email"
type="email"
validators={[
required({ msg: `Hey, email is required` }),
email(),
]}
/>
{[0, 1, 2].map((idx) => (
<TextField
key={idx}
name={`address[${idx}]`}
label={`Address ${idx + 1}`}
/>
))}
<FormDataProvider>
{({ values }) => (
<div>
<pre>JSON.stringify(values)</pre>
<pre>JSON.stringify(shapeFy(values))</pre>
</div>
)}
</FormDataProvider>
<button type="submit">Submit</button>
</Form>
)
}
values = {
'name': 'John',
'email': 'john@test.com',
'address[0]': 'First Line',
'address[1]': 'Second Line',
'address[2]': 'Postcode',
}
valuesShape = {
'name': 'John',
'email': 'john@test.com',
'address': [
'First Line',
'Second Line',
'Postcode',
]
}
Props
Form component
interface Form {
name: string,
onSubmit?: (values: Record<string, any>) => void | Promise<Record<string, any>>;
skipClientValidation?: boolean;
initialValues?: { fieldName: 'value' }
keepOnUnmount: boolean;
serialize?: boolean;
validateOnBlur?: boolean;
validateOnChange?: boolean;
disableFieldsReinit?: boolean;
validators?: {
fieldName: [
(value: any, values: Record<string, any>) => string | false,
]
};
}
Field component
interface Field {
name: string,
initialValue?: any;
parse?: (value: any) => any;
format?: (value: any) => any;
passive?: boolean;
validators?: [
(value: any, values: Record<string, any>) => string | false,
];
validateOnBlur?: boolean;
validateOnChange?: boolean;
disableFieldReinit?: boolean;
Field: ReactComponent<any>;
formName?: string;
}
IfFormValues component
Conditional rendering based on form values
interface IfFormValues {
children?: ReactNode;
form?: string;
check: (values: Record<string, any>, activeValues: Record<string, any>) => boolean;
setTo?: Record<string, any>;
resetTo?: Record<string, any>;
updateDebounce?: number;
render?: (values: Record<string, any>) => ReactElement;
}
import { IfFormValues } from 'efx-forms/IfFormValues';
const ConditionalRender = () => (
<IfFormValues check={({ age }) => age > 21 }>
<div>Hey, I am here</div>
</IfFormValues>
);
const ConditionalRenderProp = () => (
<IfFormValues
check={({ age }) => age > 21 }
render={({ age, name }) => <div>Hi, I am {name} - {age}</div>}
/>
);
FormDataProvider component
Subscribe for form values changes
interface FormDataProvider {
children: (values: ReturnType<typeof useFormData>) => ReactNode;
name?: string;
}
import { FormDataProvider } from 'efx-forms/FormDataProvider';
const FormData = () => (
<FormDataProvider>
{({ values, errors }) => <div>{values} - {errors}</div>}
</FormDataProvider>
);
IfFieldValue component
Conditional rendering based on field value
interface IfFieldValue {
children?: ReactNode;
field: string;
formName?: string;
check: (value: any) => boolean;
render?: (values: any) => ReactElement;
}
import { IfFieldValue } from 'efx-forms/IfFieldValue';
const ConditionalRender = () => (
<IfFieldValue check={(age) => age > 21 }>
<div>Hey, I am here</div>
</IfFieldValue>
);
const ConditionalRenderProp = () => (
<IfFieldValue
check={(age) => age > 21 }
render={(age) => <div>Hi, I am {age}</div>}
/>
);
FieldDataProvider component
Subscribe for field value changes
interface FieldDataProvider {
children: (values: ReturnType<typeof useFieldData>) => ReactNode;
name: string;
formName?: string;
}
import { FieldDataProvider } from 'efx-forms/FieldDataProvider';
const FieldData = () => (
<FieldDataProvider name="user.name">
{({ value, active }) => <div>{value} - {active}</div>}
</FieldDataProvider>
);
Instances
Form Instance
interface FormInstance {
domain: Domain;
name: string;
$active: Store<Record<string, boolean>>;
$activeOnly: Store<Record<string, true>>;
$activeValues: Store<Record<string, any>>;
$values: Store<Record<string, any>>;
$errors: Store<Record<string, string[]>>;
$error: Store<Record<string, string | null>>;
$valid: Store<boolean>;
$submitting: Store<boolean>;
$touched: Store<boolean>;
$touches: Store<Record<string, boolean>>;
$dirty: Store<boolean>;
$dirties: Store<Record<string, boolean>>;
config: IFormConfig;
configs: Record<string, IFieldConfig>;
erase: EventCallable<void>;
onChange: EventCallable<{ name: string; value: any; }>;
onBlur: EventCallable<{ name: string; value: any; }>;
reset: EventCallable<void>;
resetField: EventCallable<string>;
resetUntouched: EventCallable<string[]>;
setActive: EventCallable<{ name: string; value: boolean; }>;
setConfig: (cfg: IFormConfig) => void;
setFieldConfig: (cfg: IFieldConfig) => void;
setValues: EventCallable<Record<string, any>>;
submit: Effect<ISubmitArgs, ISubmitResponseSuccess, ISubmitResponseError>;
validate: EventCallable<IValidationParams>;
}
Methods / Hooks
import { getForm, useFormInstance } from 'efx-forms';
import { useForm } from 'efx-forms/useForm';
import { useFormData } from 'efx-forms/useFormData';
import { useFormValues } from 'efx-forms/useFormValues';
import { useFormStore } from 'efx-forms/useFormStore';
import { useFormStores } from 'efx-forms/useFormStores';
import { useFormMethods } from 'efx-forms/useFormMethods';
import { useField } from 'efx-forms/useField';
import { useFieldData } from 'efx-forms/useFieldData';
import { useFieldStore } from 'efx-forms/useFieldStore';
import { useStoreProp } from 'efx-forms/useStoreProp';
import { useStorePropFn } from 'efx-forms/useStorePropFn';
const formOne = getForm({ name: 'form-one' });
const formTwo = useForm();
const formThree = useFormData();
const formInst = useFormInstance();
const formErrors = useFormStore('$errors');
const [errors, values] = useFormStores(['$errors', '$values']);
const formValues = useFormValues();
const formMethods = useFormMethods();
const field = useField('field-one');
const fieldData = useFieldData('field-one');
const fieldActive = useFieldStore({
store: '$active',
name: 'user.name',
formName: 'login',
defaultValue: '',
});
const storePropValue = useStoreProp(form.$values, 'user.name', '');
const storePropFnValue = useStorePropFn(form.$values, (val) => val.name, '');
Utils
import {
domain,
truthyFy,
shapeFy,
truthyFyStore,
shapeFyStore,
flattenObjectKeys,
} from 'efx-forms/utils';
const truthyValues = truthyFy(values);
const shapedValues = shapeFy(values);
const $truthyStore = truthyFyStore($values);
const $shapedStore = shapeFyStore($values);
const initialValues = flattenObjectKeys(values);
Validators
Check validators.d.ts file to see all built-in validators and their arguments
import { required, email } from 'efx-forms/validators';
const formValidations = {
'user.name': [required()],
'user.email': [
required({ msg: 'Email is required' }),
email(),
],
}
Examples