📦Plugin Docs

@gaddario98/react-form

Una librería avanzada de React para gestionar formularios dinámicos y seguros en cuanto a tipos, construida sobre TanStack React Form con soporte completo para TypeScript, internacionalización, estado Jotai y notificaciones.

Vedi su GitHub

@gaddario98/react-form

An advanced React library for managing dynamic and type-safe forms, built on TanStack React Form with full support for TypeScript, internationalization, Jotai state management, and notifications.

✨ Features

  • 🎯 Type-Safe: Full TypeScript support with generics and automatic type inference.
  • 🚀 TanStack Form: Built on the modern, performant, and headless @tanstack/react-form.
  • 🌍 i18n Ready: Native integration with translation options for internationalization.
  • 🔔 Notifications: Built-in generic notification system for standard success and error handling.
  • 🎨 Customizable: Fully customizable containers, dynamic fields, and conditional rendering.
  • Jotai Integration: Exposes form state effectively using internally managed state atoms via @gaddario98/react-state.
  • Flexible Layouts: Includes structural injection arrays to render fields in custom views, dialogs, etc.

📦 Installation

yarn workspace @gaddario98/react-form install @tanstack/react-form @gaddario98/react-state react

Peer Dependencies

Make sure you have react installed.

🚀 Quick Start

Basic Example

The library exposes a single cohesive component, FormManager, which you can feed a configuration array to build layout-agnostic complex forms.

import React, { useMemo } from "react";
import { FormManager, FormManagerProps } from "@gaddario98/react-form";
import { Input } from "your-ui-library";

interface UserForm {
  name: string;
  email: string;
}

export const BasicForm = () => {
  const formProps = useMemo(
    (): FormManagerProps<UserForm> => ({
      defaultValues: { name: "", email: "" },
      // Fields configuration
      data: [
        {
          name: "name",
          label: "Full Name",
          rules: {
            onChange: (val) => (!val ? "Name is required" : undefined),
          },
          component: (props) => (
            <Input
              value={props.value}
              onChange={(e) => props.onChange(e.target.value)}
              error={!!props.errorMessage}
              helperText={props.errorMessage}
            />
          ),
        },
        {
          name: "email",
          label: "Email",
          component: (props) => (
            <Input
              value={props.value}
              onChange={(e) => props.onChange(e.target.value)}
              error={!!props.errorMessage}
              helperText={props.errorMessage}
            />
          ),
        },
      ],
      // Submit buttons configuration
      submit: [
        {
          component: ({ onClick, type }) => (
            <button type={type} onClick={onClick}>
              Save
            </button>
          ),
          onSuccess: async (values) => {
            console.log("Saved data:", values);
          },
        },
      ],
    }),
    [],
  );

  return <FormManager {...formProps} />;
};

📚 Core Concepts & API Reference

FormManager Component

The wrapper responsible for building your form utilizing TanStack form hooks. You pass an array of field definitions (data) and submit definitions (submit).

export type FormManagerProps<T extends FieldValues> = {
  data: Array<
    | FormManagerConfig<T>
    | ((props: FormFunctionProps<T>) => FormManagerConfig<T>)
  >;
  defaultValues: T;
  onInvalid?: (err: unknown) => void;
  submit?: Array<Submit<T>>;
  notification?: {
    success?:
      | FormNotificationMessage
      | ((res: unknown) => FormNotificationMessage);
    error?:
      | FormNotificationMessage
      | ((error: string) => FormNotificationMessage);
    ns?: string;
  };
  onValuesChange?: (
    props: T,
    setValue: (name: any, value: any) => void,
  ) => void;
  formSettings?: UseCustomFormOptions<T>;
  viewSettings?: {
    container?: React.ComponentType;
    submitContainer?: React.ComponentType;
    bodyContainer?: React.ComponentType;
    containerProps?: Record<string, unknown>;
    // ...
  };
  // ...
};

Dynamic Fields configuration (data)

Elements in the data array shape the visual layout and validation of the fields. They can be standard objects or factory functions granting you access to current form values get() and setters set().

data: [
  // 1. Static Configuration
  {
    name: "role",
    label: "User Role",
    component: (props) => <SelectComponent {...props} />,
  },
  // 2. Dynamic Configuration based on form values
  ({ get }) => ({
    name: "adminCode",
    label: "Access Code",
    hidden: get("role") !== "admin",
    rules: {
      validate: (val) =>
        !val && get("role") === "admin" ? "Required for admins" : undefined,
    },
    component: (props) => <Input {...props} />,
  }),
];

Field Rules (Validation)

Since we're using TanStack Form under the hood, standard validators functions apply natively:

rules: {
  validate: (value) => value.length < 3 ? 'Min 3 chars' : undefined,
  onChange: (value) => ...
}

Action Configuration (submit)

You can define multiple submission or action buttons that only validate a specific fields subset using the values array. This is perfect for partial updates or drafts.

submit: [
  {
    component: ({ onClick }) => (
      <Button onClick={onClick} variant="ghost">
        Cancel
      </Button>
    ),
  },
  {
    // ONLY validate these fields
    values: ["name", "address", "vatNumber"],
    component: ({ onClick, type }) => (
      <Button onClick={onClick}>Publish</Button>
    ),
    onSuccess: async (values) => {
      // Here `values` will be typed according to the `values` subset specified above
      await updateCompanyMutation(values);
    },
  },
];

View/Layout Settings (viewSettings)

Want to render your form in a Dialog? A Card? You can replace the underlying HTML wrappers cleanly without polluting your actual form configurations.

viewSettings: {
  container: DialogContainer,
  containerProps: {
    open,
    onOpenChange,
    title: 'Edit Company'
  },
  submitContainer: ({ children }) => <DialogFooter>{children}</DialogFooter>,
}

Global Notifications Configuration

FormManager exposes a notification object that intercepts onSuccess/onError scenarios natively to show toast notifications via the config context provider.

<FormManager
  notification={{
    success: { message: "Successfully updated!", type: "success" },
    error: (err) => ({ message: `Operation failed: ${err}`, type: "error" }),
  }}
  // ...
/>

🔌 Hook Alternatives

Rather than using the <FormManager /> component directly, you can also use the useFormManager hook if you need a headless implementation and custom orchestration over the built elements.

import { useFormManager } from "@gaddario98/react-form";

const { elements, formValues, setValue, errors } = useFormManager({
  data,
  formOptions: { defaultValues },
  submit,
});

You can then iterate elements which provides .element (the JSX node), .renderInFooter, .renderInHeader, .isSubmit, and .index.

📦 Global Form Configuration Context

The form configuration (such as custom containers, text translation, and notification logic) is managed globally using Jotai atoms via @gaddario98/react-state.

Instead of wrapping your app in a Context Provider, you can efficiently set the configuration anywhere (typically at the root of your application) using the useFormConfigState hook.

import { useFormConfigState } from "@gaddario98/react-form";
import { useEffect } from "react";
import { toast } from "your-toast-library";
import { useTranslation } from "react-i18next";

export const CoreAppProvider = ({ children }) => {
  const { t } = useTranslation();
  const [, setFormConfig] = useFormConfigState();

  useEffect(() => {
    // Inject your global dependencies into the form package
    setFormConfig((prev) => ({
      ...prev,
      translateText: (key, options) => t(key, options as any),
      formFieldContainer: ({ children }) => (
        <div className="p-2 border">{children}</div>
      ),
      showNotification: (msg) => toast(msg.message, { type: msg.type }),
    }));
  }, [setFormConfig, t]);

  return <>{children}</>;
};

Configuration APIs

The library exports the built atoms and hooks from config.ts so you have full control over the form's global environment:

  • useFormConfigValue(): Reads the current configuration (used internally by form fields).
  • useFormConfigState(): Returns [config, setConfig] analogous to useState.
  • useFormConfigReset(): Resets the configuration to its default empty state.
  • formConfigAtom: The raw Jotai atom if you need to manipulate it outside of the React Tree via the Jotai store.

📄 License

MIT