Skip to content

Migrating @tsed/react-formio from v2 to v3 ​

The rc branch already bundles every breaking change that will ship with @tsed/react-formio@3.x. Compared to master (v2), we moved to an ESM-only toolchain, reorganised the components into Atomic folders, replaced the Redux helper packages with hooks/contexts, refreshed peer dependencies (Choices.js, React Select, TanStack Table), and introduced Storybook/MSW-based documentation.

This guide distills the git log master..rc history into concrete steps so you can upgrade a v2 codebase with minimal friction.


Snapshot of the RC branch ​

AreaHighlights from master..rc
Build & Toolingrefactor(react-formio): migrate to esm and vitest, refactor(tailwind-formio): use vite to build package to esm, Vite/Vitest upgrade
Component architecturerefactor(react-formio): move all formio component under organisms, feat(react-formio): add button molecule, feat(tabs): implement Tabs component with keyboard navigation
Data layerfeat(react-formio): use @tanstack/react-table, feat(react-formio): add support Choicesjs and React-select layout to InputTags, feat(react-formio): register FormBuilder, FormEdit, etc to the container components
UX & Docsfeat: implement i18n support, feat: add more documentation and use lucide font instead bx icons, new Storybook docs (stories/GettingStarted.mdx, stories/FormViews.mdx, etc.)
HousekeepingRemoval of packages/react-formio-stores, packages/redux-utils, and packages/react-formio-container; CI upgrades; Chromatic/Storybook 9

Key breaking changes ​

  1. ESM-only bundles – @tsed/react-formio now ships "type": "module" outputs only. Update your bundler (Vite, Webpack ≥5, Next ≥13) or use tools with ESM support. CommonJS require() imports are no longer supported.
  2. Per-component import paths – Tree-shaking relies on importing from the component’s atomic folder, e.g. @tsed/react-formio/organisms/form/Form or @tsed/react-formio/molecules/forms/select/Select. The root barrel no longer exports every component.
  3. Tailwind 3+ baseline – All design tokens expect Tailwind ≥3.3. Tailwind v2 classes are removed. Update your tailwind.config.[jt]s plus any presets referencing the old tokens.
  4. Redux store packages removed – @tsed/react-formio-stores, @tsed/redux-utils, and @tsed/react-formio-container were deleted. Migrate to hooks (useForm, useFormioContext), TanStack Table helpers, or bring your own state management.
  5. New peer dependencies – Several UI components now depend on @formio/choices.js, react-select, @tanstack/react-table@^8, and use-debounce. Install them explicitly in your application.
  6. FormBuilder & FormViews refactor – Builder, Edit, Preview, Export, and Settings views moved under organisms/form. Form routes rely on the new FormViews composite component plus hooks (useFormBuilder, useFormEdit).
  7. I18n & accessibility updates – Tabs, tables, and forms now use lucide icons, improved keyboard navigation, and useI18n. Ensure your host app provides translation strings or fallbacks.
  8. MSW-powered stories – Storybook uses Mock Service Worker for form APIs. Local playgrounds should either run MSW (public/mockServiceWorker.js) or provide equivalent mocks.

Step-by-step migration ​

  1. Upgrade dependencies

    • Ensure Node ≥18 and Yarn 4 (Plug’n’Play) are in place.
    • Install new peers:
      bash
      yarn add @formio/choices.js @tanstack/react-table react-select react-table @tsed/tailwind-formio
    • Remove deprecated packages:
      bash
      yarn remove @tsed/react-formio-stores @tsed/redux-utils @tsed/react-formio-container
  2. Adopt ESM import patterns

    • Convert any require() usage to import.
    • Update Jest/Vitest configs to use native ESM or Vite’s tsconfigPaths.
    • Replace default imports with component paths:
      diff
      - import { FormBuilder, Form } from "@tsed/react-formio";
      + import { Form } from "@tsed/react-formio/organisms/form/Form";
      + import { FormBuilder } from "@tsed/react-formio/organisms/form/builder/FormBuilder";
    • If you want to register every molecule/organism globally (Storybook, legacy apps), import once:
      ts
      import "@tsed/react-formio/all";
  3. Update styling and tokens

    • Replace Tailwind v2 presets with the new @tsed/tailwind-formio preset and run yarn tailwind:build.
    • Ensure your global CSS loads lucide fonts (bundled with packages/tailwind-formio) or map icons in your tailwind.config.
    • Review form-specific styles (packages/tailwind-formio/styles/*.css) and import whichever layers you override.
  4. Migrate data and table usage

    • Swap legacy react-table@7 helpers to TanStack Table v8. Example:
      ts
      import { createColumnHelper } from "@tanstack/react-table";
    • Use the new molecules table hooks: @tsed/react-formio/molecules/table/hooks/useTable.
    • For lists such as Forms, Submissions, or Actions, prefer the new organisms/table/* components. They expose keyboard navigation and filter helpers out of the box.
    • Need the full diff for Table/UseTableProps? See the dedicated section “Table & useTable specifics” below for column examples, prop mappings, and TanStack-specific notes.
  5. Refactor FormBuilder flows

    • The builder and edit experiences rely on hooks:
      ts
      import { useFormBuilder } from "@tsed/react-formio/organisms/form/builder/useFormBuilder";
      import { useFormEdit } from "@tsed/react-formio/organisms/form/builder/useFormEdit";
    • FormViews encapsulates the builder, preview, export, and access settings. Replace custom routing with <FormViews /> where possible.
    • Hooks now return typed FormType/SubmissionType (renamed from the previous Schema suffix). Adjust generics accordingly.
  6. Internationalisation

    • Components expect translations via useI18n. Provide a context by wrapping your app with FormioContext or pass options.i18n.
    • Tabs and pagination include keyboard navigation out of the box; ensure no alternative handlers hijack Arrow keys.
  7. Verification

    • Re-run lint/tests to confirm ESM + Tailwind changes compile:
      bash
      yarn lint
      yarn test
      yarn storybook:start
  • For CI, update any scripts that referenced the removed packages or old Storybook CLI flags.

Table & useTable specifics ​

The table layer moved from react-table@7 (useCustomTable) to @tanstack/react-table@8. This section consolidates all API changes so teams can migrate custom tables without diffing the repo.

Snapshot of differences ​

Concernv2 (react-table@7)v3 (@tanstack/react-table@8)
Component path@tsed/react-formio/components/table/table.component@tsed/react-formio/molecules/table/Table
HookuseCustomTable (wraps useTable, useSortBy, usePagination, etc.)useTable (wraps useReactTable, getCoreRowModel, filtering + faceting helpers)
Column definitionColumn<Data> + custom ExtendedColumn metadataColumnDef<Data> with metadata in column.columnDef.meta and filters declared via meta.filter
OperationsOptional operations?: Operation[], manual cell renderoperations: Operation<Data>[] is required; hook automatically appends an “Operations” column when non-empty
Query eventsonChange?(query: QueryOptions) (filters/page/sort)onChange?(state: TableState) returns the TanStack state bag (pagination, sorting, column visibility, etc.)
Pagination inputstotalLength, pageSize, pageIndex, manualPagination, pageSizesrowCount, pageSizes, manualPagination, pageCount; Table component wires them into the shared Pagination component
FiltersPer-column ColumnFilter props, string filter idsColumn metadata (column.columnDef.meta.filter), faceted row/unique value helpers, filter components under molecules/table/filters
Custom cells/headersProps such as Cell, Row, CellHeader, CellOperations passed directlyComponents resolved via registries (getComponent("CellHeader"), CellFooter, CellOperations) with TanStack render context
Drag & dropBuilt-in via DrapNDropContainer, onDrag, onDropRemoved; implement drag-and-drop externally if still required

TableProps / UseTableProps comparison ​

v2 propertyv3 equivalentNotes
columns: ExtendedColumn<Data>[]columns: ColumnDef<Data, TValue>[]Column metadata such as filters, cell props, and visibility now lives under column.columnDef.meta (see molecules/table/interfaces/extends.ts).
data: Data[]data: Data[]Must satisfy { [key: string]: JSON } to align with FormIO data typing.
operations?: Operation[]operations: Operation<Data>[] (required)Provide [] if you have no row-level actions; the hook appends an “Operations” column when the array is non-empty.
onClick?(row, operation)onClick?(data, operation)Same behaviour, but bound to the injected operations column.
onChange?(QueryOptions)onChange?(TableState)Expect the TanStack state bag (pagination, sorting, column order, column visibility, etc.).
totalLengthrowCount (Pagination prop)Keeps the pagination footer totals accurate in server/manual mode.
manualFilters, manualSortBy, manualPaginationmanualFiltering, manualSorting, manualPaginationTanStack renamed these flags.
ColumnFilter, ArrowSort, Cell, Row, CellHeader, CellOperations propsComponents resolved via registries (getComponent("CellHeader"), getComponent("CellOperations"), etc.).Override registry entries to customise the visuals.
enableDragNDrop, onDrag, onDropRemovedWrap <Table /> with your own drag-and-drop solution if needed.
filters, sortBy, pageIndex, pageSize (controlled props)Provide TanStack state/onStateChange or rely on the built-in pagination stateControlled mode now mirrors TanStack’s API.
metadatametadata?: Record<string, unknown>Optional bag forwarded to CellOperations.

Tip: Re-export the new type to ease adoption:
export type AdminTableProps<TData extends Record<string, JSON>> = TableProps<TData>;

Code diff examples ​

Columns definition ​

diff
- import { Column } from "react-table";
+ import { ColumnDef } from "@tanstack/react-table";

- const columns: Column<User>[] = [
-   {
-     Header: "Email",
-     accessor: "email",
-     Filter: EmailFilter,
-     Cell: ({ value }) => <a href={`mailto:${value}`}>{value}</a>
-   }
- ];
+ const columns: ColumnDef<User>[] = [
+   {
+     header: "Email",
+     accessorKey: "email",
+     meta: {
+       filter: { type: "text" },
+       cellProps: { className: "text-blue-500" }
+     },
+     cell: ({ getValue }) => <a href={`mailto:${getValue<string>()}`}>{getValue<string>()}</a>
+   }
+ ];

Hook/component usage ​

diff
- import { useCustomTable, TableProps } from "@tsed/react-formio/components/table";
-
- function UsersTable(props: TableProps<User>) {
-   const table = useCustomTable({
-     ...props,
-     columns
-   });
-   // render table.tableInstance
- }
+ import { Table, TableProps } from "@tsed/react-formio/molecules/table/Table";
+
+ function UsersTable(props: TableProps<User>) {
+   return (
+     <Table
+       {...props}
+       data={props.data}
+       columns={columns}
+       operations={[
+         { id: "view", label: "View", action: "view" },
+         { id: "delete", label: "Delete", action: "delete", variant: "danger" }
+       ]}
+       onClick={(row, operation) => handleOperation(row, operation)}
+       onChange={(state) => syncQuery(state)}
+       manualPagination
+       pageSizes={[10, 25, 50]}
+       rowCount={props.rowCount}
+     />
+   );
+ }

Filters, sorting, and metadata ​

  1. Filters: Attach filter metadata via column.columnDef.meta.filter. Built-in helpers (TextField, Select, Range) live in packages/react-formio/src/molecules/table/filters, powered by TanStack’s faceted row/unique value helpers.
  2. Sorting & headers: Use column.getIsSorted() plus the registry-driven DefaultCellHeader for keyboard-accessible headers.
  3. Hidden columns: Set column.columnDef.meta.hidden = true to hide a column while keeping it in the data model (replaces ExtendedColumn.hidden).
  4. Custom cell props: Provide column.columnDef.meta.cellProps to tweak <td> attributes (className, data-*, etc.).

Query lifecycle changes ​

  • onChange emits the full TableState (pagination, sorting, column order, column pinning, column sizing, column visibility, global filter). Use it to update query params or trigger server requests.
  • Manual mode uses TanStack flags: manualPagination, manualSorting, manualFiltering, plus pageCount. Keep rowCount in sync so the pagination footer shows totals.
  • The hook injects an “Operations” column whenever operations.length > 0. Override the CellOperations registry entry for advanced layouts.

Table migration checklist ​

  1. Update imports to the new paths (@tsed/react-formio/molecules/table/Table, ColumnDef from @tanstack/react-table).
  2. Rewrite columns using accessorKey/accessorFn and move metadata to meta.
  3. Provide operations, pageSizes, and rowCount (set operations: [] if you have none).
  4. Update onChange handlers to expect TableState.
  5. Remove enableDragNDrop, onDrag, and onDrop.
  6. Run yarn lint && yarn test && yarn storybook:start to ensure the new definitions compile and render.

Component import cheat sheet ​

v2 importv3 import
import {Form} from "@tsed/react-formio";import {Form} from "@tsed/react-formio/organisms/form/Form";
import {FormBuilder} from "@tsed/react-formio";import {FormBuilder} from "@tsed/react-formio/organisms/form/builder/FormBuilder";
import {InputText} from "@tsed/react-formio";import {InputText} from "@tsed/react-formio/molecules/forms/input-text/InputText";
import {Table} from "@tsed/react-formio";import {Table} from "@tsed/react-formio/molecules/table/Table";
import {Tabs} from "@tsed/react-formio";import {Tabs} from "@tsed/react-formio/molecules/tabs/Tabs";

Dependency checklist ​

  • [ ] @tsed/react-formio@^3
  • [ ] @formio/js peer still installed
  • [ ] @formio/choices.js@^9
  • [ ] react-select@^5
  • [ ] @tanstack/react-table@^8
  • [ ] react-table@^7 (still required for backward-compatible APIs)
  • [ ] use-debounce@^10
  • [ ] @tsed/tailwind-formio@^3 and Tailwind ≥3.3

Verification & rollout ​

  1. Run yarn build (monorepo) to ensure Vite bundles succeed.
  2. Run Storybook locally (yarn storybook:start) and click through the new migration page to confirm links work.
  3. If you rely on Chromatic or CI, update snapshots after verifying there are no unexpected visual regressions.
  4. Communicate the removal of Redux store packages to downstream teams; they may need to pin v2 until ready.

  • Storybook: stories/MigrateToV3.mdx
  • README: packages/react-formio/readme.md → “Migrate to v3”
  • LLM instructions: docs/llm-migrate-v2-to-v3.md

Released under the MIT License.