Skip to content

Astroner/schematic-forms-react

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Description

Schematic forms is schema based form processing library for react writen with typescript.

Instalation

npm i --save @schematic-forms/core @schematic-forms/react

Usage

Library uses context and hooks(or render props) as a main concept of usage.

import React, { FC } from 'react'
import { useController, FormProvider, FieldConsumer } from '@schematic-forms/react'
import { Str } from '@schematic-forms/core'

export const Form: FC<{}> = () => {
    const { controller, submit, clear } = useController({
        fields: {
            email: Str(true),
            password: Str(true)
        },
        validators: {
            email: EmailValidator("INCORRECT_EMAIL")
        },
        submit: (data) => {
            console.log(data)
        }
    })

    return (
        <FormProvider controller={controller} >
            <FieldConsumer field="email">
                {({ value, setValue, error }) => (
                    <div>
                        {error}
                        <input value={value} onChange={e => setValue(e.target.value)} />
                    </div>
                )}
            </FieldConsumer>
            <FieldConsumer field="password">
                {({ value, setValue, error }) => (
                    <div>
                        {error}
                        <input value={value} onChange={e => setValue(e.target.value)} />
                    </div>
                )}
            </FieldConsumer>
            <ErrorConsumer>
                {({ hasError }) => (
                    <button disabled={hasError} onClick={submit} >Submit</button>
                )}
            </ErrorConsumer>
            <button onClick={clear}>Clear</button>
        </FormProvider>
    )
}

What's going on here, yeah? Let me explain. Lets start with useController() hook. it takes config and returns object like { controller, submit, clear }

type config = {
    fields: {
        [key: string]: SchemaType
    },
    validators: {
        [key: string]: (value: FieldValue) => void | Error
    },
    submit: (data: Data) => void | { [key: string]: Error } | Promise<void | { [key: string]: Error }>
}

From top into bottom:

  • fields - form fields. keys - common string, values something like Str(), Bool(), Num(), that u can import from @schematic-forms/core
  • validators - object with field validation functions.
  • submit - function that will be executed when all form conditions are met. submit will get form data and should return void or error map (also it can returns a Promise).

Then we'll take FormProvider component. It's simple context provider requiring controller that useController() returns, no more.

FieldConsumer is dirty(but easy) way to change fields. takes only one prop: field - field name to control. As children takes function with following type

type RenderFunction = (
    props: { value: any, setValue: (nextValue: any
) => void, error: null | string }) => ReactNode

value and setValue react's useState() like functions. error will provide errors on this field

ErrorConsumer represents exception handling.

type ErrorConsumerProps = {
    field?: string
    code?: string
}

If no props were provided ErrorConsumer will react on any exception in any fields. As children u can pass RenderFunction like in example above or just ReactNode.

Value types

Types you can pass into "fields" object.

const fields = {
    str: Str(required: boolean, defaultValue: string),
    num: Num(required: boolean, defaultValue: number),
    object: Obj(
        objectSchema: ObjectSchema, 
        required: boolean, 
        defaultValue: ObjectSchemaRealization
    ),
    enum: Enum(variant: Variant[], required: true, defaultValue: Variant),
    array: Arr(
        arrType: SchemaFieldType, 
        require: boolean, 
        defaultValue: SchemaFieldType[]
    ),
    mix: Mix(TypeToMix[], required: boolean, defaultValue: TypeToMix)
}
// Object type example
const book = Obj({
    title: Str(true),
    count: Num(true),
    author: Obj({
        name: Str(true),
        email: Str(true)
    })
}, true)

// Enum type example
const gender = Arr<["M", "F"]>(["M", "F"], true) 
/* 
You have to use generic argument because typescript identifies arrays like ["M", "F"] as a string array.
*/

// Array type example
const titles = Arr(Str(true), true)

// Mix type example
const mixedType = Mix([
    Str(true),
    Obj({ title: Str(true) }, true)
])

Hooks

useForm

useForm is a right way to change controller fields and create custom form fields.

function useForm<ValueType>(
    name: string, 
    nullValue?: ValueType
): [
    ValueType | undefined, // value
    | (nextVal: ValueType // change function
    | ((prevValue?: ValueType) => ValueType | undefined)) => void,
    string | null // error
]
// Example
const FormInput: FC<{ name: string }> = ({ name }) => {
    const [value, setValue, error] = useForm(name, "");
    
    return (
        <div>
            {error}
            <input 
                value={value} 
                onChenge={e => setValue(e.target.value)} 
            />
        </div>
    )
}
useHasError

Returns true if controller has errors and false if not.

// ... react component with useController hook
const hasErrors = useHasError(controller)
return (
    <div>
        {hasErrors && "ERRORS!"}
        {/* ... */}
    </div>
)
usePending

If your submit function returns Promise you probably want to know it`s status. usePending returns { isPending: true } if promise is pending and { isPending: false } if not.

// ... react component with useController hook
const { isPending } = usePending(controller)
return (
    <div>
        {isPending && "LOADING..."}
        {/* ... */}
    </div>
)
useValue

Returns controller field value

// ... react component with useController hook
const { controller } = useController({
    fields: {
        email: Str(true)
    }
})

const email = useValue(controller, "email")

return (
    <div>
        email: {email}
    </div>
)

Components

ErrorConsumer

Consumes controller errors using context.

interface ErrorConsumerProps {
    children: 
        | ReactNode 
        | ((
            props: { hasError: true, errorCode: string } | { hasError: false, errorCode: null }
        ) => ReactNode)
    
    /** @description observable controller field*/
    field?: string
    
    /** @description error type */
    error?: string
}
  • Without "field" prop component will trigger on any error in any field.
  • "error" prop specify which error type in "field" should trigger re-render.
// Example
// ... react component with useController hook
const { controller } = useController({
    fields: {
        email: Str(true)
    },
    validators: EmailValidator("EMAIL_ERROR")
})

return (
    <FormProvider controller={controller}>
        <ErrorConsumer field={email}>
            Error in E-mail field
        </ErrorConsumer>
        <ErrorConsumer field={email} error="REQUIRED">
            E-mail required
        </ErrorConsumer>
        <ErrorConsumer field={email} error="EMAIL_ERROR">
            E-mail validation failed
        </ErrorConsumer>
        <ErrorConsumer field={email}>
            {({ hasError, errorCode }) => !hasError ? null : (
                <div>
                    {errorCode}
                </div>
            )}
        </ErrorConsumer>
    </FormProvider>
)
PendingConsumer

Keeping the controllers status in check.

// Example
// ... react component with useController hook
const { controller } = useController({
    fields: {
        email: Str(true)
    },
    validators: EmailValidator("EMAIL_ERROR"),
    submit: async () => {
        await delay(5000)
    }
})

return (
    <FormProvider controller={controller}>
        <PendingConsumer>
            Loading...
        </PendingConsumer>
        <PendingConsumer>
            {({ isPending }) => (
                <button disabled={isPending}>
                    submit
                </button>
            )}
        </PendingConsumer>
    </FormProvider>
)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published