# React Wrapper Dev

> Use when developing or modifying the @handsontable/react-wrapper package - React components, hooks, settings mapping, selection preservation during updateSettings, and the wrapper's TypeScript prop types and generated .d.ts. Use this whenever a task touches HotTableProps/HotColumnProps typing or IDE autocomplete for <HotTable>/<HotColumn> props, the declaration build, or a report that React/TypeScript users get no prop suggestions - even if the wrapper is not named explicitly.

- Skill: `handsontable/react-wrapper-dev` (Agent Skill)
- Install (CLI): `npx skillmds@latest add handsontable/react-wrapper-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/handsontable/react-wrapper-dev/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: handsontable (https://skillmd.com/u/handsontable)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/handsontable/react-wrapper-dev

---


# React Wrapper Development

## Package location

`wrappers/react-wrapper/`

## Components

- **HotTable** - the public component users import. Renders a container div and bootstraps Handsontable.
- **HotTableInner** - a `forwardRef` wrapper that handles the actual instance lifecycle.
- **HotColumn** - declarative column configuration as a child of HotTable.
- **HotEditor** - renders a custom editor component inside a React portal.

## Architecture

A `useRef()` hook holds the live Handsontable instance. It is exposed to parent components through `useImperativeHandle()`, so consumers can call `hotInstance.current` to access the grid API directly.

`SettingsMapper.getSettings()` converts React props into a plain Handsontable settings object. Every prop change triggers `updateSettings()` on the instance.

**Critical rule:** When calling `updateSettings()`, you must preserve and restore the current selection. Before the call, snapshot the selection with `selection.exportSelection()`. After the call, restore it with `selection.importSelection()`. Forgetting this causes the selection to reset on every prop change.

## Custom hooks and portals

- `useHotEditor()` - a hook for building component-based cell editors. It gives the editor component access to the editor lifecycle (open, close, getValue, setValue).
- React portals are used to render React components inside Handsontable cells (for renderers and editors). A React context propagates the Handsontable instance to these portals.

## Build and test

- **Build system:** Rollup 4 producing CommonJS, ES module, UMD, and minified outputs.
- **Tests:** Jest with React Testing Library.
- **Run tests:** `npm run test --prefix wrappers/react-wrapper`
- **Important:** Build core first with `npm run build --prefix handsontable`. Wrappers consume `handsontable/tmp/`, not `dist/`.

## Key files

| File | Purpose |
|---|---|
| `src/hotTable.tsx` | Public HotTable component |
| `src/hotTableInner.tsx` | Inner component with instance lifecycle |
| `src/settingsMapper.ts` | Converts React props to Handsontable settings |
| `src/hotColumn.tsx` | Declarative column config component |
| `src/hotEditor.tsx` | Custom editor portal component |

## TypeScript prop types and the declaration build (read before touching `src/types.tsx`)

The published `.d.ts` files are generated by `scripts/prepare-types.mjs`, which runs this package's **own `typescript` devDep — currently `3.8.2` (2020)** — and deliberately swallows `tsc` errors, so a mangled declaration still reports "prepared successfully."

- **Define modern type helpers in the core package and import them here.** TS 3.8 cannot emit 4.1+ syntax such as key-remapping (`{ [K in keyof T as ...]: ... }`); it silently produces garbage like `{ [K in keyof T]: ; }`. Keep such helpers in `handsontable/src` (built with modern TS) and import them, e.g. `RemoveIndexSignature` lives in `handsontable/src/settings.ts` and reaches the wrapper via `handsontable/base`.
- **After changing any type in `src/`, verify the emitted declaration.** Pack the core and wrapper (`npm pack`), install both into a throwaway project, and `tsc --noEmit` a file that uses the props — this is the reliable signal, since the build itself hides declaration errors.
- **Strip the index signature before `Omit`/`Pick` on a settings type.** `GridSettings`/`ColumnSettings` carry a `[key: string]: any` escape hatch for plugin/meta keys. Wrap the input in `RemoveIndexSignature<T>` first (as `ReplaceRenderersEditors` does) so the named options survive `Omit` — without it `keyof` widens to `string`, every option name is dropped, and IDE autocomplete inside `<HotTable>`/`<HotColumn>` breaks. Build column props from `RemoveIndexSignature<GridSettings>` and override `data` with `ColumnSettings['data']` — do this in the wrapper rather than making core `ColumnSettings` strict, because tightening a shipped core type breaks existing loose column configs (`columns: [{ validator: (v: string) => … }]`) across every framework. (Interface `extends` keeps the options but can't override `renderer`/`editor`/`data`, whose types differ.) Finally, re-add `& { [key: string]: any }` to the prop type so undeclared cell-type/plugin options (`correctFormat`, `datePickerConfig`, …) stay assignable; named options keep their real types regardless, exactly like `React.CSSProperties`. A prop type without this hatch fails on real configs (e.g. a `date` column passing `correctFormat`).
- **Lint through the monorepo-level lint command.** This package has no `lint` script or local `.eslintrc`, so run the root lint (which supplies the TS/JSX-aware parser) rather than a per-file invocation.

## React StrictMode gotcha

React StrictMode causes a double mount (mount -> unmount -> mount). This means Handsontable gets initialized twice. The wrapper must correctly destroy the instance on unmount and create a fresh one on the second mount. If cleanup is incomplete, the second mount can fail or leak memory. Always verify that `destroy()` is called on unmount and that no stale references persist.

## Rules

- No business logic in wrappers. Data transformation, validation, and grid behavior belong in `handsontable/src/`.
- Cross-platform npm scripts: use Node.js `.mjs` helpers instead of bash-only constructs (see `scripts/prepare-types.mjs` as reference).

