@ackee/mateus

Antd components as a form fields + redux-saga flow for easier handle of redux-form form's submit

Usage no npm install needed!

<script type="module">
  import ackeeMateus from 'https://cdn.skypack.dev/@ackee/mateus';
</script>

README

ackee|Mateus

GitHub license"> CI Status PRs Welcome Dependency Status

Mateus

Antd components as a form fields + redux-saga flow for easier handle of redux-form form's submit.

Name of package Mateus refers to name of the apostole Matthew which is a patron of all office workers. Mateus is a Portuguese equivalent for Matthew.

Table of contents

Installation

Using npm:

npm i -S @ackee/mateus

API

Action creators

submitForm(formId: string, submitActionCreator: () => object): function

  • formId Form unique identificator

  • submitActionCreator Action creator for form specific submit action

  • Returns Form submit handler that is used for handleSubmit prop of redux-form Form.

    Example - use submitForm in react-redux container

    import { connect } from 'react-redux';
    import { bindActionCreators, compose } from 'redux';
    
    import UserForm from './components/UserForm';
    
    const createUserAction = () => ({
        type: 'CREATE_USER'
    })
    
    const formId = 'createUserForm';
    
    export default compose(
        connect(
            (state) => ({
                initialValues: {
                },
            }),
            dispatch => ({
                onSubmit: bindActionCreators(
                    formActions.submitForm(formId, createUserAction),
                    dispatch,
                ),
            }),
        ),
        reduxForm({
            form: formId,
        }),
    )(UserForm);
    

Action types

FORM_SUBMIT

import { actionTypes } from '@ackee/mateus';
import { takeEvery } from 'redux-saga/effects';

takeEvery(actionTypes.FORM_SUBMIT, action => {
    const { data, form } = action;

    console.log('Submitted form', form);
});

Sagas

submitFormSaga(): void

Form saga ensures you receive all neccessary data and methods (eg. for handling form flow) in form submit handler. Just use it as your only saga, or in list of sagas.

In parallel with form submit saga, you will likely want to use submitForm action creator.

Examples - Usage

import { submitFormSaga as formSaga } from 'ackee-frontend-toolkit/sagas';

// main saga
export default function* () {
    yield all([
        // ... other sagas
        formSaga(),
    ]);
}

Complete example of using form saga flow

Handling form submit using our submitForm saga in redux-form is easy and enable separation of concerns.

It's all divided into few simple steps

  1. Plug submitFormSaga into main saga
  2. Use submitForm action as a handler of form submit
  3. Catch action provided as a second parameter to submitForm and hadle it by custom saga.
  4. Manage whole redux-form submit process, including startSubmit, stopSubmit and reset, in custom saga.

Example is a bit shortened and simplified:

// config/index.js
export default {
    // ...
    forms: {
        addUser: 'addUserForm',
    }
    api: {
        user: '/api/v1/user',
    }
}
// actions/user.js
export const addUserAction = () => ({
    type: 'ADD_USER'
})
// components/UserForm.js
const UserForm = ({ handleSubmit, submitting }) => (
    <Form onSubmit={handleSubmit}>
        <Field
            disabled={submitting}
            id="firstname"
            name="firstname"
            component={Input}
            type="text"
        />
        <Field
            disabled={submitting}
            id="lastname"
            name="lastname"
            component={Input}
            type="text"
        />
        <button type="submit">Add user</button>
    </Form>
);

UserForm.propTypes = {
    // these props are automatically supplied by reduxForm()
    handleSubmit: PropTypes.func.isRequired,
    submitting: PropTypes.bool.isRequired,
    invalid: PropTypes.bool.isRequired,
};

export default UserForm;
// containers/UserForm.js
import { submitForm } from '@ackee/mateus';
import { addUserAction } from '../actions/user';
import config from '../config';

export default compose(
    connect(
        (state) => ({
            initialValues: {},
        }),
        dispatch => ({
            onSubmit: bindActionCreators(
                submitForm(config.forms.addUser, addUserAction),
                dispatch,
            ),
        }),
    ),
    reduxForm({
        form: config.forms.addUser,
    }),
)(UserForm);
// sagas/index.js
import { submitFormSaga } from '@ackee/mateus';
import userSaga from '../sagas/userSaga'; 
export default function* () {
    yield all([
        submitFormSaga(),
        userSaga(),
    ]);
}
// sagas/userSaga.js
import config from '../config';

function* handleAddUserForm(action) {
    const { data, startSubmit, stopSubmit, reset } = action;

    yield startSubmit();
    try {
        yield api.post(
            config.api.user,
            {
                firstname: data.firstname,
                lastname: data.lastname,
            },
        );
        yield stopSubmit();
        yield reset();
    } catch (e) {
        const errors = { 'user add error': e };
        yield stopSubmit(errors);
    }
}

export default function* () {
    yield all([
        takeEvery('ADD_USER', handleAddUserForm),
    ]);
}

Form fields

All form fields are available either as an Antd component (eg. TextInput) wrapped with FormItem or the same, but enclosed into redux-form Field.

List of fields:


Text

  • TextInput accept Antd Input props.

  • TextField accept same props as Input plus all the props you can pass to redux-form Field.

    import { TextInput, TextField } from '@ackee/mateus';
    

TextArea

  • TextAreaInput accept Antd TextArea props.

  • TextAreaField accept same props as Input plus all the props you can pass to redux-form Field.

    import { TextAreaInput, TextAreaField } from '@ackee/mateus';
    

Select

  • SelectInput accept Antd Select props, but instead of passing options as children components, they'are passed as an array in props.

    Default name for that prop is options and it's shape is { label: ReactNode, value: string|number }. The names can be changed by specifying optionsKey, labelKey or valueKey prop (look at example below).

  • SelectField accept same props as Input plus all the props you can pass to redux-form Field.

    import { SelectInput, SelectField } from '@ackee/mateus';
    
    const select = (
        <SelectInput 
            options={[
                { label: 'Option1': value: 1 },
                { label: <span>Option2</span>: value: 2 },
            ]}
        />
    );
    
    const selectWithCustomNames = (
        <SelectInput 
            optionsKey="users"
            labelKey="name"
            valueKey="id"
            users={[
                { name: 'Anakin', id: 'siths1' },
                { name: 'Luke', id: 'jedis1' },
            ]}
        />
    );
    

NumberInput

  • NumberInput accept Antd InputNumber props.

  • NumberField accept same props as Input plus all the props you can pass to redux-form Field.

    import { NumberInput, NumberField } from '@ackee/mateus';
    

Switch

  • SwitchInput accept Antd Switch props.

  • SwitchField accept same props as Input plus all the props you can pass to redux-form Field.

    import { SwitchInput, SwitchField } from '@ackee/mateus';
    

Slider

  • SliderInput accept Antd Slider props.

  • SliderField accept same props as Input plus all the props you can pass to redux-form Field.

    import { SliderInput, SliderField } from '@ackee/mateus';
    

Radio

This is a bit confusing, because RadioInput is actually RadioGroup - in most cases we want to render group of radios to let user select one option so we don't need an individual Radio. If from any reason you realy need only one Radio without RadioGroup just feel free to use it directly from antd.

  • RadioInput accept Antd RadioGroup props, but with several differencies

    • the definition options prop is a bit changed. It lacks disabled field so the shape is just { label: ReactNode, value: string|number }.
    • on the other hand, as same as for Select you can specify custom names for props and the shape keys by specifying optionsKey, labelKey or valueKey prop (look at example below).
    • you can pass button prop of type boolean that force use of radio buttons instead of plain radios.
  • RadioField accept same props as Input plus all the props you can pass to redux-form Field.

    import { RadioInput, RadioField } from '@ackee/mateus';
    
    const selectYourFavourite = (
        <RadioInput 
            optionsKey="southPark"
            labelKey="name"
            valueKey="color"
            southPark={[
                { name: 'Kenny', color: 'orange' },
                { name: 'Cartman', color: 'fat' },
                { name: 'Stan', color: 'blue' },
                { name: 'Kyle', color: 'green' },
            ]}
            button
        />
    );
    

CheckboxGroup

  • CheckboxGroupInput accept Antd CheckboxGroup props.

  • CheckboxGroupField accept same props as Input plus all the props you can pass to redux-form Field.

    import { CheckboxGroupInput, CheckboxGroupField } from '@ackee/mateus';
    

DatePicker & TimePicker

  • Both pickers accept props from shared API plus:

    • DatePickerInput accept Antd DatePicker props.
    • TimePickerInput accept Antd TimePicker props.
    • both accept displayFormat prop which is the same as format prop defined in picker's API. But since redux-form Field has also format prop and so Input's one would be overriden when using DatePickerField or TimePickerField, wee need to provide an alternative.
  • DatePickerField/TimePickerField accept same props as DatePickerInput/TimePickerInput plus all the props you can pass to redux-form Field.

    import { DatePickerInput, DatePickerField, TimePickerInput, TimePickerField } from '@ackee/mateus';
    

pickerEnhancer

  • TBD

    import { pickerEnhancer } from '@ackee/mateus';
    

HOC

wrapWithField(component: React.Component, injectProps?: InjectPropsType): React.Component

Hight order component that wrap supplied component with redux-form's Field and inject extra properties into it (see InjectPropsType). This is useful for form fields that are not part of @ackee/mateus.

  • InjectPropsType is either an props object or function (props: object): object that receives props passed to component and returns extra props that will be also passed to component

    Example - use it fo Antd Rate component

    const Rate = wrapWithField(
        AntdRate,
        {
            character: <Icon type="heart" />
        }
    );
    
    const RateForm = ({ handleSubmit, invalid }) => (
        <Form onSubmit={handleSubmit}>
            <Rate />
        </Form>
    );