npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@undermuz/use-form

v1.0.29

Published

React library for build forms

Downloads

125

Readme

@undermuz/use-form

React library for build forms

Install

npm i @undermuz/use-form

NPM: @undermuz/use-form

Update

npm update @undermuz/use-form

Basic usage

Storybook

Setup form

For example, setup a login form with username and password fields with rules Rules work like: !yourFn(filedValue) && errorText, your should provide an array: [Rule1, Rule2, RuleN] Every rule is array: [yourFnArray, errorText], yourFnArray is array of functions: yourFn(filedValue: unknown) => boolean

This example uses built-in JS function Boolean to validate input's values

    const formConfig: IUseFormSettings = {
        fields: {
            username: {
                label: "Login",
                rules: [[[Boolean], "Username is required"]],
            },
            password: {
                label: "Password",
                rules: [[[Boolean], "Password is required"]],
            },
        },
    }

    ...

    const form = useForm(formConfig)

You should wrapp your inputs and components witch use form-hooks by FormContext.Provider

    <FormContext.Provider value={form}>
    ...
    </FormContext.Provider>

Connect input-like components to the form

To connect any form component (or just a component) you have to wrapp it by ConnectToForm and provide a name

Provided component have to receive value prop, onChange prop and call onChange with new value

    <ConnectToForm name="FIELD_NAME">
        {/*Your component*/}
    </ConnectToForm>

ConnectToForm provides current field's value to your component, and wait new value through onChange

Browser's input

Storybook: browsers inputs example

    type InputProps = Partial<IConnectedProps> & {
        type?: string
        placeholder?: string
    }

    //Short-version
    const FormInputV1: React.FC<InputProps> = ({
        inputProps = {}, //Provides by ConnectToForm
        ...rest
    }) => {
        return (
            <label style={styles}>
                {inputProps.label}:
                <input {..._.pick(rest, ["type", "placeholder"])} {...inputProps} />
            </label>
        )
    }

    //Full-version
    const FormInputV2: React.FC<InputProps> = (props) => {
        const {
            type = "text",
            placeholder = "",
            label, //Provides by ConnectToForm
            name, //Provides by ConnectToForm
            value, //Provides by ConnectToForm
            onChange, //Provides by ConnectToForm
            onBlur, //Provides by ConnectToForm
        } = props

        return (
            <div style={styles}>
                <label htmlFor={name}>{label}:</label>
                <input
                    type={type}
                    id={name}
                    name={name}
                    value={value}
                    placeholder={placeholder}
                    onChange={(e) => onChange?.(e.target.value)}
                    onBlur={() => onBlur?.()}
                />
            </div>
        )
    }

    ...
    
    <FormContext.Provider value={form}>
        <ConnectToForm name="username">
            <FormInputV1 placeholder="Enter your login" />
        </ConnectToForm>

        <ConnectToForm name="password">
            <FormInputV2
                type="password"
                placeholder="Enter your password"
            />
        </ConnectToForm>
    </FormContext.Provider>

Ui framework's input

Storybook: chakra-ui example

    type InputProps = Partial<IConnectedProps> & {
        type?: string
        placeholder?: string
        description?: string
    }

    const FormField: FC<PropsWithChildren<InputProps>> = (props) => {
        const {
            label,
            description = null,
            errors, //Provides by ConnectToForm
            children,
            hasError = false, //Provides by ConnectToForm
        } = props

        return (
            <FormControl isInvalid={hasError}>
                <FormLabel>{label}</FormLabel>

                {children}

                {description !== null && !hasError && (
                    <FormHelperText>{description}</FormHelperText>
                )}

                {errors?.map((errorText, index) => {
                    if (typeof errorText !== "string") {
                        return null
                    }

                    return (
                        <FormErrorMessage key={index}>{errorText}</FormErrorMessage>
                    )
                })}
            </FormControl>
        )
    }

    const FormInput: React.FC<InputProps> = (props) => {
        const {
            type = "text",
            placeholder = "",
            value, //Provides by ConnectToForm
            onChange, //Provides by ConnectToForm
            onBlur, //Provides by ConnectToForm
        } = props

        return (
            <FormField {...props}>
                <Input
                    type={type}
                    placeholder={placeholder}
                    onChange={(e) => onChange?.(e.target.value)}
                    onBlur={() => onBlur?.()}
                    value={value}
                />
            </FormField>
        )
    }

    const ErrorBlock = () => {
        const errors = useFormErrors()
        const fields = useFormFields()

        return (
            <>
                {Object.keys(errors).map((name) => (
                    <>
                        <p key={name}>{fields?.[name] || name}:</p>
                        <ul>
                            {errors[name].map((error, i) => (
                                <li key={i}>{error as string}</li>
                            ))}
                        </ul>
                    </>
                ))}
            </>
        )
    }

    ...

    <VStack alignItems={"flex-start"}>
        <ConnectToForm name="username">
            <FormInput placeholder="Enter your login" />
        </ConnectToForm>

        <ConnectToForm name="password">
            <FormInput
                type="password"
                placeholder="Enter your password"
            />
        </ConnectToForm>

        <FormSubmit
            as={Button}
            onSend={onSend}
            onSucceed={onSucceed}
        >
            {(status: EnumFormSubmitStatus) => {
                if (status === EnumFormSubmitStatus.Sending) {
                    return "Sending..."
                }

                return "Send"
            }}
        </FormSubmit>

        <IfForm hasErrors>
            <ErrorBlock />
        </IfForm>
    </VStack>

Third-party components

Storybook: Date-picker example

    const formConfig: IUseFormSettings = {
        fields: {
            date: {
                label: "Date picker",
                rules: [[[Boolean], "Date is required"]],
                initialValue: new Date(),
            },
            rangeDates: {
                label: "Date picker: Range",
                rules: [[[Boolean], "Username is required"]],
                initialValue: [new Date(), new Date()],
            },
        },
    }

    const form = useForm(formConfig)
    
    ...

    type InputProps = Partial<IConnectedProps> & {
        type?: string
        placeholder?: string
        description?: string
    }

    const FormField: FC<PropsWithChildren<InputProps>> = (props) => {
        const {
            label,
            description = null,
            errors, //Provides by ConnectToForm
            children,
            hasError = false, //Provides by ConnectToForm
        } = props

        return (
            <FormControl isInvalid={hasError}>
                <FormLabel>{label}</FormLabel>

                {children}

                {description !== null && !hasError && (
                    <FormHelperText>{description}</FormHelperText>
                )}

                {errors?.map((errorText, index) => {
                    if (typeof errorText !== "string") {
                        return null
                    }

                    return (
                        <FormErrorMessage key={index}>{errorText}</FormErrorMessage>
                    )
                })}
            </FormControl>
        )
    }

    const FormDatePicker: React.FC<InputProps & { isRange?: boolean }> = (
        props
    ) => {
        const {
            isRange = false,
            name, //Provides by ConnectToForm
            value, //Provides by ConnectToForm
            onChange, //Provides by ConnectToForm
        } = props

        return (
            <FormField {...props}>
                {!isRange && (
                    <SingleDatepicker
                        name={name}
                        date={value}
                        onDateChange={(date) => onChange?.(date)}
                    />
                )}

                {isRange && (
                    <RangeDatepicker
                        name={name}
                        selectedDates={value}
                        onDateChange={(date) => onChange?.(date)}
                    />
                )}
            </FormField>
        )
    }

    ...

    <ConnectToForm name="date">
        <FormDatePicker />
    </ConnectToForm>

    <ConnectToForm name="rangeDates">
        <FormDatePicker isRange />
    </ConnectToForm>

Input's states

    const Input: React.FC<IConnectedProps> = ({
        inputProps = {}, //Provides by ConnectToForm
        label, //Provides by ConnectToForm
        errors, //Provides by ConnectToForm
        isSucceed, //Provides by ConnectToForm
        hasError, //Provides by ConnectToForm
        isFocused, //Provides by ConnectToForm
        isTouched, //Provides by ConnectToForm
        isFilled, //Provides by ConnectToForm
        isDisabled //Provides by ConnectToForm
        ...rest // You've provided
    }) => {
        return (
            <label>
                {label}

                <input
                    {..._.pick(rest, ["type", "placeholder", "etc"])}
                    {...inputProps}
                    className={isSucceed ? "succeed" : hasError ? "has-error" : "default"}
                />

                {/*  Other states */}
                {isFocused && "Tip: type something funny"}
                {isTouched && "You've already touched this field"}
                {isFilled && "You've already filled this field"}
                {isDisabled && "This field is disabled"}

                {/* Field errors */}
                {hasError && <>
                    <span>Errors:</span>
                    <ul>
                        {errors.map((error: string, i: number) => (
                            <li key={i}>{error}</li>
                        ))}
                    </ul>
                </>}
            </label>
        )
    }

Form's states

    <IfForm>
        <p>Show when form is default</p>
    </IfForm>

    <IfForm isSuccess>
        <p>Form has been sent success</p>
    </IfForm>

    <IfForm isCanceling>
        <p>Form has sent unsuccess</p>
    </IfForm>

    <IfForm isSending>
        <p>Form is sending now</p>
    </IfForm>

    <IfForm hasErrors>
        <p>Form has errors</p>
    </IfForm>

You can get form's values and errors directly through form variable:

    const form = useForm(/*Form config*/)

    const { values, errors } = form

    useEffect(() => {
        console.log("[Form][Values]", values)
    }, [values])

    useEffect(() => {
        console.log("[Form][Errors]", errors)
    }, [errors])

Or by context inside FormContext.Provider:

    const { values, errors } = useFormContext()

    useEffect(() => {
        console.log("[Form][Values]", values)
    }, [values])

    useEffect(() => {
        console.log("[Form][Errors]", errors)
    }, [errors])

Submit

Create callbacks

    const form = useForm(/*Form config*/)

    ...

    const onSend = useCallback(async (values: IValues) => {
        console.log("Login data", values)

        await sendValuesToTheServer(values)
    }, [])

    const onSucceed = useCallback(() => {
        console.log("Login completed")
    }, [])

    const onError = useCallback(() => {
        console.log("Login failed")
    }, [])

    const submit = useFormSubmit(onSend, onSucceed, onError)

Get submit callback by hook


    const submit = useFormSubmit(onSend, onSucceed, onError)

    ...

    <Button disabled={form.isSending || form.isCanceling || form.hasErrors} onClick={submit}>
        Submit
    </Button>

OR Get submit by component

    //Component version

    <FormSubmit onSend={onSend} onSucceed={onSucceed} onError={onError}>
        {(status: EnumFormSubmitStatus) => {
            if (status === EnumFormSubmitStatus.Sending) {
                return "Sending..."
            }

            if (status === EnumFormSubmitStatus.Canceling) {
                return "Failed"
            }

            if (status === EnumFormSubmitStatus.Succeed) {
                return "Succeed"
            }

            return "Submit"
        }}
    </FormSubmit>

Controlled form

You can control form's values from outside by providing value and onChange to useForm's config

const [value, onChange] = useState<IValues>(() => {
    return {
        username: "",
        password: ""
    }
})

const form = useForm({
    fields: {
        username: {
            label: "Login",
            rules: [[[Boolean], "Username is required"]],
        },
        password: {
            label: "Password",
            rules: [[[Boolean], "Password is required"]],
        },
    },
    value,
    onChange
})

You can get more control

/*
    FORM_ACTIONS = {
        SET_VALUES
        SET_VALUE
        SET_TESTS
        SET_TOUCHED_FIELD
        SET_TOUCHED
        SET_ERRORS
        SET_FIELDS
        SET_VALIDATE
        SET_IS_SENDING
        SET_IS_CANCELING
        SET_IS_SUCCESS
        SET_SEND_ERROR
        VALIDATE_FORM
        SEND_FORM
    }
*/

const [formConfig, formState] = useFormCoreParams({
    fields: {
        username: {
            label: "Login",
            rules: [[[Boolean], "Username is required"]],
        },
        password: {
            label: "Password",
            rules: [[[Boolean], "Password is required"]],
        },
    },
    options: {
        middlewares: [
            /* --> */createYourCustomMiddleware()/* <-- */
        ]
    }
})

const form = useFormCore(formConfig, formState)

const setValue = useCallback((values: any) => {
    if (isDebug) console.log("[useCustomForm][setValue]", values)

    formState.dispatch({
        type: FORM_ACTIONS.SET_VALUES,
        payload: {
            values,
            /* --> */yourCustomPayload: "some-additional-info"/* <-- */
        },
    })
}, [])

You can get even more control

    const useCustomFormState = (props: IFormConfig): FormState => {
        const initialState = useMemo(() => getInitialState(props), [])

        const middlewares = useMemo(
            () => [
                ...(props?.middlewares || []),

                /* --> */YOUR_CUSTOM_MIDDLEWARE(props)/* <-- */,

                /*  You can remove default middlewares: */
                //createValidating(props),
                //createSend(props),
            ],
            []
        )

        const [state, dispatch, store] = useReducer<IFormState>(
            formReducer,
            initialState,
            middlewares
        )

        return { state, dispatch, store }
    }

    const useCustomFormParams = (
        formSettings: IUseFormSettings
    ): [IFormConfig, FormState] => {
        const formConfig = useFormConfigBySettings(formSettings)
        const formState = useCustomFormState(formConfig)

        return [formConfig, formState]
    }

    ...
        const [formConfig, formState] = useCustomFormParams({
            fields: {
                username: {
                    label: "Login",
                    rules: [[[Boolean], "Username is required"]],
                },
                password: {
                    label: "Password",
                    rules: [[[Boolean], "Password is required"]],
                },
            }
        })

        const form = useFormCore(formConfig, formState)
    ...

Examples

const Input: React.FC<IConnectedProps> = ({
    type = "text",
    placeholder = "",
    onChange,
    label,
    value,
}) => {
    return (
        <>
            <label>{label}</label>
            <InputGroup fullWidth size="Small">
                <input
                    type={type}
                    placeholder={placeholder}
                    onChange={(e) => onChange?.(e.target.value)}
                    value={value}
                />
            </InputGroup>
        </>
    )
}

const ErrorBlock = () => {
    const errors = useFormErrors()
    const fields = useFormFields()

    return (
        <>
            {Object.keys(errors).map((name) => (
                <>
                    <p key={name}>{fields?.[name] || name}:</p>
                    <ul>
                        {errors[name].map((error: string, i: number) => (
                            <li key={i}>{error}</li>
                        ))}
                    </ul>
                </>
            ))}
        </>
    )
}

const LoginForm = () => {
    const form = useForm({
        fields: {
            username: {
                label: "Login",
                rules: [[[Boolean], "Username is required"]],
            },
            password: {
                label: "Password",
                rules: [[[Boolean], "Password is required"]],
            },
        },
    })

    const onSend = useCallback((values: IValues) => {
        console.log("Login data", values)
    }, [])

    const onSucceed = useCallback(() => {
        console.log("Login completed")
    }, [])

    return (
        <FormContext.Provider value={form}>
            <ConnectToForm name="username">
                <Input placeholder="Enter your login" />
            </ConnectToForm>

            <ConnectToForm name="password">
                <Input type="password" placeholder="Enter your password" />
            </ConnectToForm>

            <FormSubmit onSend={onSend} onSucceed={onSucceed}>
                {(status: EnumFormSubmitStatus) => {
                    if (status === EnumFormSubmitStatus.Sending) {
                        return "Sending..."
                    }

                    return "Send"
                }}
            </FormSubmit>

            <IfForm hasErrors>
                <ErrorBlock />
            </IfForm>
        </FormContext.Provider>
    )
}