Find & replace
The data-grid-find-replace add-on: a panel that finds text in the grid and replaces one match or all matches as one undoable change.
Users fix the same typo in many cells without editing each cell. data-grid-find-replace adds a
panel with a Find field and a Replace field. useDataGridFindReplace returns the plugin that
mounts the panel and binds Mod+F to open Find and Ctrl+H to open Find and Replace.
Press Ctrl+H inside the grid to open the panel.
pnpm dlx shadcn@latest add @gridcn/data-grid-find-replace
Pass the plugin to the grid:
import { useDataGridFindReplace } from "@/components/data-grid-find-replace/data-grid-find-replace";
const findReplace = useDataGridFindReplace();
<DataGridProvider {...grid} columns={columns} plugins={[findReplace.plugin]}>
<DataGridRoot>
<DataGridHeader />
<DataGridBody />
</DataGridRoot>
</DataGridProvider>;findReplace.open(mode) opens the panel from your own button. mode is "replace" (the default) to show the Replace field, or "find" to show Find only. findReplace.close() closes it.
Find
The Find field writes the grid's quick search. The highlights, the next and previous buttons, and the cells that Replace changes are always the same cells. The toolbar search shows the same text.
The search is a literal text match. It never reads the text as a pattern. Two icon toggles sit inside the right side of the Find field and change the match. A pressed toggle has a filled background and a small dot at its corner.
- Match case matches letter case. It is off by default.
- Whole cell matches only cells that equal the Find text. It is off by default.
The Previous and Next buttons follow the field. Their tooltips name the keys: Enter goes to the next match and Shift+Enter to the previous one.
The status line is the last row of the panel and keeps one line height. Text that does not fit is
cut off, and the title holds the full text. It shows 2 of 4 cells for the current match,
No results when nothing matches, and Type to search while the Find field is empty. The number
gets a + when the highlight list stops at its cap. The Replace row adds its own row to the panel
and removes it again.
Find ignores leading and trailing spaces in the Find text. Pass the same options to the store with
actions.setSearch(text, { matchCase, wholeCell }). The options stay set until the next call that
sets them.
Replace
A cell is matched on its search text. This is the text the user sees, for example the label of a
select choice. Replace swaps the Find text in that text and then converts the result with the
column type's fromText. A select label maps back to its value. A number cell parses again.
- Replace changes the current match, then moves to the next match. When the active cell is a match, Replace changes that cell.
- Replace all changes every matching cell in the view.
Each click writes one change with the source "replace". One undo reverts it. Selection and
active cell stay where they are.
Replace skips a cell when:
- the column or the row is read-only,
- the replaced text cannot become a value of the type (replacing
5withxin anumbercolumn leaves the cell as it is), - the replaced text is longer than 100,000 characters,
- the column's
validaterejects the new value.onValidationErrorreceives these cells with the source"replace".
The Replace row holds the Replace field, the Replace button, and the Replace all button. The same
status line shows how many cells changed and how many were skipped. With an async validate, it
shows Validating… until the batch settles.
Where Replace works
Replace covers the visible columns of the rows in the view. Hidden columns, filtered-out rows,
pinned rows, and unloaded lazy rows never change. With scope: "selection", it covers only the
selected cells.
One Replace all changes at most replaceAllLimit cells. When more cells match, the status line
says so. Click Replace all again to continue. The next click skips the cells the earlier one
changed, even when the replacement contains the find text. A change to the find text, the
replacement, or an option starts over.
The chevron button at the start of the Find row shows and hides the Replace field. The panel keeps
the Find text when you toggle it. Every open sets the Replace field: Mod+F and
open("find") hide it, and Ctrl+H and open("replace") show it.
When the grid is readOnly, the Find field works. The chevron button is not shown, and Replace stays disabled. While a cell editor
is open, Replace does nothing.
A sort or filter on a replaced column does not move rows while the panel is open. The view
reconciles on the next sort or filter change. See reorder in
Streaming updates.
Keyboard
| Key | Where | Action |
|---|---|---|
| Mod+F | grid | Opens the panel with the cursor in Find. The Replace field stays hidden. |
| Ctrl+H | grid | Opens the panel with the cursor in Find and the Replace field shown. |
| Enter | Find | Goes to the next match. |
| Shift+Enter | Find | Goes to the previous match. |
| Enter | Replace | Replaces the current match. |
| Mod+Enter | Replace | Replaces all matches. |
| Escape | panel | Closes the panel. Focus returns to the grid when the panel opened while the grid had focus. |
Ctrl is the physical Ctrl key on every platform. Cmd+H hides the
app on macOS, and a page cannot take it. Mod is Cmd on macOS and Ctrl
elsewhere. Outside the grid, Mod+F opens the browser's own find bar. To change
a key, bind openFindReplace or openFind in the grid's keymap.
When the grid also shows DataGridSearch, Mod+F with focus in the grid opens
this panel, not the toolbar search. The panel and the toolbar search show the same text.
Options
| Prop | Type | Default | Description |
|---|---|---|---|
scope | "view" | "selection" | "view" | Which cells Replace covers. |
replaceAllLimit | number | 10000 | Most cells one Replace all changes. Clamped to 1 to 100,000. |
planReplace
planReplace(state, find, replacement, options) is the pure function behind the buttons. Its
options are scope, replaceAllLimit, matchCase, wholeCell, only, and skip. It
returns { patches, skipped, remaining } and writes nothing. Pass patches to
actions.updateCells(patches, { source: "replace" }) to write them. options.only limits the plan
to one { rowId, columnId } cell. options.skip takes a function that receives a { rowId, columnId }
cell and returns true to leave the cell out of the plan. Each entry of skipped has a reason:
"readonly", "unparseable", "too-long", or "no-occurrence". "no-occurrence" marks a matched
cell whose text holds no occurrence of the Find text to replace. Find and replacement text longer than 1,000 characters is cut.
Labels
The panel strings live under labels.findReplace. See Internationalization.
Ctrl+H opens the browser history in some browsers
The panel calls preventDefault on Ctrl+H while the grid has focus. A
browser that reserves the key still opens its history. Bind another key in keymap, or use
findReplace.open() from a button.
Related
- Sorting, filtering & search: the quick search that Find shares
- Undo & redo: records the
"replace"source by default - Events & state: the
sourcefield andonValidationError - Keybindings: remap
openFindReplaceandopenFind