0.3.1 • Published 5 years ago

@ghoti/forms v0.3.1

Weekly downloads
3
License
MIT
Repository
github
Last release
5 years ago

Ghoti Forms

This is a (relatively) unopinionated library for common form functionality like validation and metadata tracking like dirty checking. It does not provide any form elements itself, just the building blocks to wrap your favorite ones in it's functionality with minimal boilerplate.

The main Ghoti project uses this library, but it can be used independently by installing @ghoti/forms from npm or yarn. Check out that side of the repository for some more complex examples.

Current Functionality

  • Track meta data like isPristine/isDirty
  • Synchronous validation
  • Hooks to wrap your favorite form components via formElement()

Planned functionality

  • A less aggressive validation strategy, currently it validates on every change. It would be nice to only trigger it at certain element events like blur
  • Asynchronous validation. The foundation is theoretically there, I just haven't needed it yet.

Hello World

Making a form is fairly simple. Here is an obligatory Hello world to show a simple example of capturing text in a field named helloName and writing that value back out on the screen.

import {Form, FormState} from '@ghoti/forms';
import TextInput from './TextInput'; // A component wrapped in the formElement HOC, more on that later...

<>
    <Form
        onSubmit={this.onFormSubmit}
        formState={formState}
        onChange={(formState) => this.setState({formState}))}
    >
        <div>
            <label htmlFor="helloName">What is your name?</label>
            <TextInput name="helloName" />
        </div>
    </Form>
    <div>
        Hello {formState.value.helloName}
    </div>
</>

Notice there is no need to set up value and on change props on each element in your form. Everything is driven by the formState and onChange props on the form itself.

Usage

Creating your first form element component

Wrapping a component in the formElement HOC is simple. formElement takes one paramenter options: FormElementOptions and returns a function that you call with 1 parameter, the Component: React.ComponentType | React.SFC you want to wrap.

At a minimum your component shold take 2 props. value and onChange

  • value: any - the currently value of the element
  • onChange: (newValue: any) => void - Call this function with the new value of the element when it changes.
// TextInput.tsx

import * as React from 'react';
import { formElement, FormElementProps } from '@ghoti/forms';

const noOp = () => {};

// Give on change and value default values just in case
function TextInput ({ onChange = noOp, value = '' }: FormElementProps<string>) {
    return (
        <input type="text" value={value} onChange={(e) => onChange(e.target.value)} />
    )
}

export default formElement<FormElementProps<string>, string>()(TextInput);

If you're using typescript, formElement requires 2 generic types, the propType and the value type. If anyone knows a better way to infer this information I am open to pull requests.

See here for a full example.

Form Example

//CreateUserPage.tsx
import * as React from 'react';
import { FormState, createFormState, Form, Group } from '@ghoti/forms';
import TextInput from './TextInput'


interface CreateUserPageState {
    formState: FormState,
}

class CreateUserPage extends React.Component<{}, CreateUserPageState> {
    constructor(props: object) {
        super(props);

        this.state = {
            formState: createFormState(),
        };
    }

    onSubmit = (newValue: any) => {
        // create the user
    }

    onFormChange = (newState: FormState) => {
        this.setState({ formState: newState });
    }

    render() {
        return (
            <div>
                <h1>Create User</h1>
                <Form
                    onSubmit={this.onSubmit}
                    formState={this.state.formState}
                    onChange={this.onFormChange}
                >
                    <div>
                        <label htmlFor="firstName">First Name</label>
                        <TextInput name="firstName" required />
                    </div>
                    <div>
                        <label htmlFor="lastName">Last Name</label>
                        <TextInput name="lastName" required />
                    </div>
                    <div>
                        <h3>Address</h3>
                        <Group name="address">
                            <div>
                                <label htmlFor="address1">Address 1</label>
                                <TextInput name="address1" required />
                            </div>
                            <div>
                                <label htmlFor="address2">Address 2</label>
                                <TextInput name="address2" />
                            </div>
                            <div>
                                <label htmlFor="city">City</label>
                                <TextInput name="city" />
                                <label htmlFor="state">State</label>
                                <TextInput name="state" />
                                <label htmlFor="zip">Zip</label>
                                <TextInput name="zip" />
                            </div>
                        </Group>
                    </div>
                    <button type="submit">Create User</button>
                </Form>
            </div>
        )
    }
}

Validation

TODO: Creating a validation function and handling errors.

  • Max length example
  • Displaying error messages

Design Principles

  • Be opinionated on how forms should work
  • Be unopinionated on things like markup, you do you.
  • Minimal boilerplate
0.3.1

5 years ago

0.3.0

5 years ago

0.2.0

6 years ago