475

useField

Hook to manage a form field

statemediumtest coverage

Account settings

Update your public profile and preferences.

Choose your preferred language
import type { SubmitEvent } from 'react';

import { useField } from '@siberiacancode/reactuse';
import { CheckIcon, ChevronDownIcon } from 'lucide-react';

import { cn } from '@/utils/lib';

const LANGUAGES = [
  { value: 'en', label: 'English' },
  { value: 'ru', label: 'Russian' },
  { value: 'de', label: 'German' }
];

const EMAIL_PATTERN = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;

const Demo = () => {
  const nameField = useField('siberiacancode', { validateOnBlur: true });
  const emailField = useField('hello@reactuse.org', { validateOnBlur: true });
  const bioField = useField('Building open-source React hooks ✨');
  const languageField = useField('en');
  const notificationsField = useField(true);
  const publicField = useField(false);

  const onSubmit = (event: SubmitEvent<HTMLFormElement>) => {
    event.preventDefault();
  };

  return (
    <section className='demo-ui flex w-full max-w-md flex-col gap-1 p-4'>
      <div className='mb-3 flex flex-col gap-1'>
        <h2 className='text-foreground text-sm font-semibold'>Account settings</h2>
        <p className='text-muted-foreground text-xs'>Update your public profile and preferences.</p>
      </div>

      <form className='flex flex-col gap-4' onSubmit={onSubmit}>
        <div className='flex flex-col gap-1.5'>
          <label className='text-foreground text-xs font-medium' htmlFor='name'>
            Display name
          </label>
          <input
            className='border-border bg-card text-foreground rounded-md border px-3 py-2 text-sm outline-none'
            id='name'
            placeholder='Your name'
            {...nameField.register({
              required: 'Name is required',
              minLength: { value: 2, message: 'At least 2 characters' }
            })}
          />
          {nameField.error && <span className='text-destructive text-xs'>{nameField.error}</span>}
        </div>

        <div className='flex flex-col gap-1.5'>
          <label className='text-foreground text-xs font-medium' htmlFor='email'>
            Email
          </label>
          <input
            className='border-border bg-card text-foreground rounded-md border px-3 py-2 text-sm outline-none'
            id='email'
            placeholder='you@example.com'
            type='email'
            {...emailField.register({
              required: 'Email is required',
              pattern: { value: EMAIL_PATTERN, message: 'Invalid email format' }
            })}
          />
          {emailField.error && <span className='text-destructive text-xs'>{emailField.error}</span>}
        </div>

        <div className='flex flex-col gap-1.5'>
          <label className='text-foreground text-xs font-medium' htmlFor='bio'>
            Bio
          </label>
          <textarea
            className='border-border bg-card text-foreground min-h-[72px] resize-none rounded-md border px-3 py-2 text-sm outline-none'
            id='bio'
            placeholder='Tell something about yourself...'
            {...bioField.register()}
          />
        </div>

        <div className='border-border flex flex-col gap-3 border-t pt-4'>
          <label className='flex cursor-pointer items-start justify-between gap-3'>
            <div className='flex flex-col gap-0.5'>
              <span className='text-foreground text-xs font-medium'>Email notifications</span>
              <span className='text-muted-foreground text-[11px]'>
                Receive product updates and release notes
              </span>
            </div>
            <span className='border-border bg-muted has-[:checked]:bg-foreground has-[:checked]:border-foreground relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border transition-colors'>
              <input className='peer sr-only' type='checkbox' {...notificationsField.register()} />
              <span className='bg-background absolute left-0.5 size-3.5 rounded-full transition-transform peer-checked:translate-x-4' />
            </span>
          </label>

          <div className='flex items-center justify-between gap-3'>
            <div className='flex flex-col gap-0.5'>
              <label className='text-foreground text-xs font-medium' htmlFor='language'>
                Language
              </label>
              <span className='text-muted-foreground text-[11px]'>
                Choose your preferred language
              </span>
            </div>
            <div className='relative'>
              <select
                className='border-border bg-card text-foreground w-32 appearance-none rounded-md border py-1.5 pr-7 pl-3 text-xs outline-none'
                id='language'
                {...languageField.register()}
              >
                {LANGUAGES.map((language) => (
                  <option key={language.value} value={language.value}>
                    {language.label}
                  </option>
                ))}
              </select>
              <ChevronDownIcon className='text-muted-foreground pointer-events-none absolute top-1/2 right-2 size-3.5 -translate-y-1/2' />
            </div>
          </div>
        </div>

        <label className='flex cursor-pointer items-start gap-3'>
          <span
            className={cn(
              'border-input has-[:checked]:bg-foreground has-[:checked]:border-foreground relative mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-[4px] border transition-colors'
            )}
          >
            <input className='peer sr-only' type='checkbox' {...publicField.register()} />
            <CheckIcon
              className='text-background size-3 opacity-0 peer-checked:opacity-100'
              strokeWidth={3}
            />
          </span>
          <div className='flex flex-col gap-0.5'>
            <span className='text-foreground text-xs font-medium'>Make profile public</span>
            <span className='text-muted-foreground text-[11px]'>
              Anyone on the internet can see your profile
            </span>
          </div>
        </label>

        <div className='flex justify-end'>
          <button type='submit'>Save changes</button>
        </div>
      </form>
    </section>
  );
};

export default Demo;

Installation

pnpm add @siberiacancode/reactuse

Usage

const { register, getValue, setValue, reset, dirty, error, setError, clearError, touched, focus, watch } = useField();

Type Declarations

import type { RefObject } from 'react';

export interface UseFieldOptions {
  /** The auto focus */
  autoFocus?: boolean;
  /** The initial touched */
  initialTouched?: boolean;
  /** The validate on blur */
  validateOnBlur?: boolean;
  /** The validate on mount */
  validateOnChange?: boolean;
  /** The validate on mount */
  validateOnMount?: boolean;
}

export interface UseFieldRegisterParams {
  /** The min value validation */
  max?: {
    value: number;
    message: string;
  };
  /** The max length validation */
  maxLength?: {
    value: number;
    message: string;
  };
  /** The max value validation */
  min?: {
    value: number;
    message: string;
  };
  /** The min length validation */
  minLength?: {
    value: number;
    message: string;
  };
  /** The pattern validation */
  pattern?: {
    value: RegExp;
    message: string;
  };
  /** The required validation */
  required?: string;
  /** The custom validation */
  validate?: (value: string) => string | true | Promise<string | true>;
}

export interface UseFieldReturn<Value> {
  /** The dirty state */
  dirty: boolean;
  /** The error state */
  error?: string;
  /** The input ref */
  ref: RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null>;
  /** The set error function */
  touched: boolean;
  /** The set error function */
  clearError: () => void;
  /** The focus function */
  focus: () => void;
  /** The get value function */
  getValue: () => Value;
  /** The register function */
  register: (params?: UseFieldRegisterParams) => {
    onBlur: () => void;
    onChange: () => void;
    ref: (
      node: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null | undefined
    ) => void;
  };
  /** The reset function */
  reset: () => void;
  /** The set error function */
  setError: (error: string) => void;
  /** The  set value function */
  setValue: (value: Value) => void;
  /** The watch function */
  watch: () => Value;
}

API

Parameters

NameTypeDefaultNote
params.initialValueValue""Initial value
params.initialTouchedbooleanfalseInitial touched state
params.autoFocusbooleanfalseAuto focus
params.validateOnChangebooleanfalseValidate on change
params.validateOnBlurbooleanfalseValidate on blur
params.validateOnMountbooleanfalseValidate on mount

Returns

UseFieldReturn<Value>

Contributors

ddebabinhhywax

Last updated on