Skip to content

fix(vue-table): prevent unnecessary DOM mutations in FlexRender for function renderer#6200

Open
Mini-ghost wants to merge 1 commit intoTanStack:mainfrom
Mini-ghost:fix/vue-flexrender-remount
Open

fix(vue-table): prevent unnecessary DOM mutations in FlexRender for function renderer#6200
Mini-ghost wants to merge 1 commit intoTanStack:mainfrom
Mini-ghost:fix/vue-flexrender-remount

Conversation

@Mini-ghost
Copy link

🎯 Changes

This PR fixes an issue in Vue FlexRender where function renderers can cause unnecessary DOM mutations.

Problem

When columns are created with computed(() => [...]), every dependency change recreates the whole column definitions, including new cell function references.

At the same time, FlexRender handles function renderers with h(props.render, props.props). When props.render is a function, Vue treats it as a component type. Because the function reference changes after each re-evaluation, Vue sees it as a different component and triggers an unnecessary unmount/remount. As a result, the DOM can still be mutated even when the cell content has not changed.

This can be observed in the Chrome DevTools Elements panel: after typing in the search input, the <td> for Alice's Phone cell is still marked as updated even though its content remains the same.

Mini reproduction: https://stackblitz.com/edit/vitejs-vite-xxp2iyaa?file=src%2FApp.vue&terminal=dev

Cause

  • columns is created with computed(() => [...]), so each re-evaluation creates new column and renderer references
  • FlexRender passes function renderers to h(), so Vue treats them as component types
  • Because the function reference is not stable, Vue sees it as a different component and performs an unnecessary DOM patch / remount

Solution

This change handles function renderers and component renderers differently:

  • If props.render is a function, it calls props.render(props.props) directly
  • If props.render is an object, it uses h(props.render, props.props)

This avoids treating a plain function renderer as a component type, which helps prevent unnecessary DOM mutations.

Additional note

This issue can also be easier to trigger depending on how the user defines columns.

If columns is placed inside computed(() => [...]), every dependency change recreates all column definitions, including new header / cell function references:

const columns = computed(() => [
  columnHelper.accessor('name', {
    header: `Name (${filteredData.value.length})`,
  }),
  columnHelper.accessor('phone', {
    header: 'Phone',
    cell: ({ getValue }) => getValue(),
  }),
])

If it is changed to a stable constant array, and the dynamic part is moved into header: () => ..., it can avoid recreating renderer references on every re-evaluation. This can be used as a temporary workaround on the user side:

const columns = [
  columnHelper.accessor('name', {
    header: () => `Name (${filteredData.value.length})`,
  }),
  columnHelper.accessor('phone', {
    header: 'Phone',
    cell: ({ getValue }) => getValue(),
  }),
]

However, this is only a user-side workaround. It does not fix the root cause, which is that FlexRender treats a function renderer as a component type.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

⚠️ No Changeset found

Latest commit: 5d35b06

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

Important

Review skipped

Too many files!

This PR contains 295 files, which is 145 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9fc2f045-1c49-4c29-900a-79ab166f3efd

📥 Commits

Reviewing files that changed from the base of the PR and between 88f0f3a and 5d35b06.

⛔ Files ignored due to path filters (5)
  • examples/angular/column-resizing-performant/src/favicon.ico is excluded by !**/*.ico
  • examples/angular/editable/src/favicon.ico is excluded by !**/*.ico
  • examples/angular/expanding/src/favicon.ico is excluded by !**/*.ico
  • examples/angular/row-dnd/src/favicon.ico is excluded by !**/*.ico
  • examples/angular/sub-components/src/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (295)
  • .changeset/config.json
  • .gitattributes
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/pull_request_template.md
  • .github/workflows/autofix.yml
  • .github/workflows/pr.yml
  • .github/workflows/release.yml
  • .npmrc
  • .nvmrc
  • README.md
  • docs/api/core/cell.md
  • docs/api/core/column.md
  • docs/api/core/header-group.md
  • docs/api/core/header.md
  • docs/api/core/row.md
  • docs/api/core/table.md
  • docs/api/features/filters.md
  • docs/api/features/global-filtering.md
  • docs/api/features/pinning.md
  • docs/api/features/row-selection.md
  • docs/config.json
  • docs/framework/angular/angular-table.md
  • docs/framework/react/guide/table-state.md
  • docs/guide/cells.md
  • docs/guide/column-defs.md
  • docs/guide/column-faceting.md
  • docs/guide/column-filtering.md
  • docs/guide/column-ordering.md
  • docs/guide/column-pinning.md
  • docs/guide/column-sizing.md
  • docs/guide/column-visibility.md
  • docs/guide/columns.md
  • docs/guide/custom-features.md
  • docs/guide/data.md
  • docs/guide/expanding.md
  • docs/guide/features.md
  • docs/guide/filters.md
  • docs/guide/fuzzy-filtering.md
  • docs/guide/global-faceting.md
  • docs/guide/global-filtering.md
  • docs/guide/grouping.md
  • docs/guide/header-groups.md
  • docs/guide/headers.md
  • docs/guide/pagination.md
  • docs/guide/pinning.md
  • docs/guide/row-pinning.md
  • docs/guide/row-selection.md
  • docs/guide/rows.md
  • docs/guide/sorting.md
  • docs/guide/tables.md
  • docs/guide/virtualization.md
  • docs/overview.md
  • examples/angular/basic/package.json
  • examples/angular/basic/src/app/app.component.ts
  • examples/angular/basic/src/main.ts
  • examples/angular/column-ordering/package.json
  • examples/angular/column-ordering/src/app/app.component.ts
  • examples/angular/column-ordering/src/main.ts
  • examples/angular/column-pinning-sticky/package.json
  • examples/angular/column-pinning-sticky/src/app/app.component.ts
  • examples/angular/column-pinning-sticky/src/main.ts
  • examples/angular/column-pinning/package.json
  • examples/angular/column-pinning/src/app/app.component.ts
  • examples/angular/column-pinning/src/main.ts
  • examples/angular/column-resizing-performant/.devcontainer/devcontainer.json
  • examples/angular/column-resizing-performant/.editorconfig
  • examples/angular/column-resizing-performant/.gitignore
  • examples/angular/column-resizing-performant/README.md
  • examples/angular/column-resizing-performant/angular.json
  • examples/angular/column-resizing-performant/package.json
  • examples/angular/column-resizing-performant/src/app/app.component.html
  • examples/angular/column-resizing-performant/src/app/app.component.ts
  • examples/angular/column-resizing-performant/src/app/app.config.ts
  • examples/angular/column-resizing-performant/src/app/makeData.ts
  • examples/angular/column-resizing-performant/src/app/resizable-cell.ts
  • examples/angular/column-resizing-performant/src/assets/.gitkeep
  • examples/angular/column-resizing-performant/src/index.html
  • examples/angular/column-resizing-performant/src/main.ts
  • examples/angular/column-resizing-performant/src/styles.scss
  • examples/angular/column-resizing-performant/tsconfig.app.json
  • examples/angular/column-resizing-performant/tsconfig.json
  • examples/angular/column-resizing-performant/tsconfig.spec.json
  • examples/angular/column-visibility/package.json
  • examples/angular/column-visibility/src/app/app.component.ts
  • examples/angular/column-visibility/src/main.ts
  • examples/angular/editable/.devcontainer/devcontainer.json
  • examples/angular/editable/.editorconfig
  • examples/angular/editable/.gitignore
  • examples/angular/editable/README.md
  • examples/angular/editable/angular.json
  • examples/angular/editable/package.json
  • examples/angular/editable/src/app/app.component.html
  • examples/angular/editable/src/app/app.component.ts
  • examples/angular/editable/src/app/app.config.ts
  • examples/angular/editable/src/app/editable-cell.ts
  • examples/angular/editable/src/app/makeData.ts
  • examples/angular/editable/src/assets/.gitkeep
  • examples/angular/editable/src/index.html
  • examples/angular/editable/src/main.ts
  • examples/angular/editable/src/styles.scss
  • examples/angular/editable/tsconfig.app.json
  • examples/angular/editable/tsconfig.json
  • examples/angular/editable/tsconfig.spec.json
  • examples/angular/expanding/.devcontainer/devcontainer.json
  • examples/angular/expanding/.editorconfig
  • examples/angular/expanding/.gitignore
  • examples/angular/expanding/README.md
  • examples/angular/expanding/angular.json
  • examples/angular/expanding/package.json
  • examples/angular/expanding/src/app/app.component.html
  • examples/angular/expanding/src/app/app.component.ts
  • examples/angular/expanding/src/app/app.config.ts
  • examples/angular/expanding/src/app/expandable-cell.ts
  • examples/angular/expanding/src/app/makeData.ts
  • examples/angular/expanding/src/assets/.gitkeep
  • examples/angular/expanding/src/index.html
  • examples/angular/expanding/src/main.ts
  • examples/angular/expanding/src/styles.scss
  • examples/angular/expanding/tsconfig.app.json
  • examples/angular/expanding/tsconfig.json
  • examples/angular/expanding/tsconfig.spec.json
  • examples/angular/filters/package.json
  • examples/angular/filters/src/app/app.component.ts
  • examples/angular/filters/src/app/debounced-input.directive.ts
  • examples/angular/filters/src/app/table-filter.component.ts
  • examples/angular/filters/src/main.ts
  • examples/angular/grouping/package.json
  • examples/angular/grouping/src/app/columns.ts
  • examples/angular/grouping/src/main.ts
  • examples/angular/row-dnd/.devcontainer/devcontainer.json
  • examples/angular/row-dnd/.editorconfig
  • examples/angular/row-dnd/.gitignore
  • examples/angular/row-dnd/README.md
  • examples/angular/row-dnd/angular.json
  • examples/angular/row-dnd/package.json
  • examples/angular/row-dnd/src/app/app.component.css
  • examples/angular/row-dnd/src/app/app.component.html
  • examples/angular/row-dnd/src/app/app.component.ts
  • examples/angular/row-dnd/src/app/app.config.ts
  • examples/angular/row-dnd/src/app/drag-handle-cell.ts
  • examples/angular/row-dnd/src/app/makeData.ts
  • examples/angular/row-dnd/src/assets/.gitkeep
  • examples/angular/row-dnd/src/index.html
  • examples/angular/row-dnd/src/main.ts
  • examples/angular/row-dnd/src/styles.scss
  • examples/angular/row-dnd/tsconfig.app.json
  • examples/angular/row-dnd/tsconfig.json
  • examples/angular/row-dnd/tsconfig.spec.json
  • examples/angular/row-selection-signal/package.json
  • examples/angular/row-selection-signal/src/app/app.component.ts
  • examples/angular/row-selection-signal/src/main.ts
  • examples/angular/row-selection/package.json
  • examples/angular/row-selection/src/app/app.component.ts
  • examples/angular/row-selection/src/main.ts
  • examples/angular/signal-input/package.json
  • examples/angular/signal-input/src/app/app.component.ts
  • examples/angular/signal-input/src/app/person-table/person-table.component.ts
  • examples/angular/signal-input/src/main.ts
  • examples/angular/sub-components/.devcontainer/devcontainer.json
  • examples/angular/sub-components/.editorconfig
  • examples/angular/sub-components/.gitignore
  • examples/angular/sub-components/README.md
  • examples/angular/sub-components/angular.json
  • examples/angular/sub-components/package.json
  • examples/angular/sub-components/src/app/app.component.html
  • examples/angular/sub-components/src/app/app.component.ts
  • examples/angular/sub-components/src/app/app.config.ts
  • examples/angular/sub-components/src/app/expandable-cell.ts
  • examples/angular/sub-components/src/app/makeData.ts
  • examples/angular/sub-components/src/assets/.gitkeep
  • examples/angular/sub-components/src/index.html
  • examples/angular/sub-components/src/main.ts
  • examples/angular/sub-components/src/styles.scss
  • examples/angular/sub-components/tsconfig.app.json
  • examples/angular/sub-components/tsconfig.json
  • examples/angular/sub-components/tsconfig.spec.json
  • examples/lit/basic/package.json
  • examples/lit/basic/src/main.ts
  • examples/lit/column-sizing/package.json
  • examples/lit/column-sizing/src/main.ts
  • examples/lit/filters/package.json
  • examples/lit/filters/src/main.ts
  • examples/lit/row-selection/package.json
  • examples/lit/row-selection/src/main.ts
  • examples/lit/sorting-dynamic-data/.gitignore
  • examples/lit/sorting-dynamic-data/README.md
  • examples/lit/sorting-dynamic-data/index.html
  • examples/lit/sorting-dynamic-data/package.json
  • examples/lit/sorting-dynamic-data/src/main.ts
  • examples/lit/sorting-dynamic-data/src/makeData.ts
  • examples/lit/sorting-dynamic-data/tsconfig.json
  • examples/lit/sorting-dynamic-data/vite.config.js
  • examples/lit/sorting/package.json
  • examples/lit/sorting/src/main.ts
  • examples/lit/virtualized-rows/package.json
  • examples/lit/virtualized-rows/src/main.ts
  • examples/qwik/basic/package.json
  • examples/qwik/basic/src/main.tsx
  • examples/qwik/filters/package.json
  • examples/qwik/filters/src/main.tsx
  • examples/qwik/row-selection/package.json
  • examples/qwik/row-selection/src/main.tsx
  • examples/qwik/sorting/package.json
  • examples/qwik/sorting/src/main.tsx
  • examples/react/basic/package.json
  • examples/react/basic/src/main.tsx
  • examples/react/bootstrap/package.json
  • examples/react/bootstrap/src/main.tsx
  • examples/react/column-dnd/package.json
  • examples/react/column-dnd/src/main.tsx
  • examples/react/column-groups/package.json
  • examples/react/column-groups/src/main.tsx
  • examples/react/column-ordering/package.json
  • examples/react/column-ordering/src/main.tsx
  • examples/react/column-pinning-sticky/package.json
  • examples/react/column-pinning-sticky/src/main.tsx
  • examples/react/column-pinning/package.json
  • examples/react/column-pinning/src/main.tsx
  • examples/react/column-resizing-performant/package.json
  • examples/react/column-resizing-performant/src/main.tsx
  • examples/react/column-sizing/package.json
  • examples/react/column-sizing/src/main.tsx
  • examples/react/column-visibility/package.json
  • examples/react/column-visibility/src/main.tsx
  • examples/react/custom-features/package.json
  • examples/react/custom-features/src/main.tsx
  • examples/react/editable-data/package.json
  • examples/react/editable-data/src/main.tsx
  • examples/react/expanding/package.json
  • examples/react/expanding/src/main.tsx
  • examples/react/filters-faceted/package.json
  • examples/react/filters-faceted/src/main.tsx
  • examples/react/filters-fuzzy/package.json
  • examples/react/filters-fuzzy/src/main.tsx
  • examples/react/filters/package.json
  • examples/react/filters/src/main.tsx
  • examples/react/full-width-resizable-table/package.json
  • examples/react/full-width-resizable-table/src/main.tsx
  • examples/react/full-width-table/package.json
  • examples/react/full-width-table/src/main.tsx
  • examples/react/fully-controlled/package.json
  • examples/react/fully-controlled/src/main.tsx
  • examples/react/grouping/package.json
  • examples/react/grouping/src/main.tsx
  • examples/react/kitchen-sink/package.json
  • examples/react/kitchen-sink/src/App.tsx
  • examples/react/kitchen-sink/src/components/ActionButtons.tsx
  • examples/react/kitchen-sink/src/components/CustomTable.tsx
  • examples/react/kitchen-sink/src/components/Filter.tsx
  • examples/react/kitchen-sink/src/hooks.tsx
  • examples/react/kitchen-sink/src/main.tsx
  • examples/react/kitchen-sink/src/tableModels.tsx
  • examples/react/material-ui-pagination/package.json
  • examples/react/material-ui-pagination/src/actions.tsx
  • examples/react/material-ui-pagination/src/main.tsx
  • examples/react/pagination-controlled/package.json
  • examples/react/pagination-controlled/src/fetchData.ts
  • examples/react/pagination-controlled/src/main.tsx
  • examples/react/pagination/package.json
  • examples/react/pagination/src/main.tsx
  • examples/react/query-router-search-params/package.json
  • examples/react/query-router-search-params/src/api/user.ts
  • examples/react/query-router-search-params/src/components/debouncedInput.tsx
  • examples/react/query-router-search-params/src/components/table.tsx
  • examples/react/query-router-search-params/src/hooks/useFilters.ts
  • examples/react/query-router-search-params/src/main.tsx
  • examples/react/query-router-search-params/src/routes/index.tsx
  • examples/react/query-router-search-params/src/utils/cleanEmptyParams.ts
  • examples/react/row-dnd/package.json
  • examples/react/row-dnd/src/main.tsx
  • examples/react/row-pinning/package.json
  • examples/react/row-pinning/src/main.tsx
  • examples/react/row-selection/package.json
  • examples/react/row-selection/src/main.tsx
  • examples/react/sorting/package.json
  • examples/react/sorting/src/main.tsx
  • examples/react/sub-components/package.json
  • examples/react/sub-components/src/main.tsx
  • examples/react/virtualized-columns-experimental/.gitignore
  • examples/react/virtualized-columns-experimental/README.md
  • examples/react/virtualized-columns-experimental/index.html
  • examples/react/virtualized-columns-experimental/package.json
  • examples/react/virtualized-columns-experimental/src/index.css
  • examples/react/virtualized-columns-experimental/src/main.tsx
  • examples/react/virtualized-columns-experimental/src/makeData.ts
  • examples/react/virtualized-columns-experimental/tsconfig.json
  • examples/react/virtualized-columns-experimental/vite.config.js
  • examples/react/virtualized-columns/index.html
  • examples/react/virtualized-columns/package.json
  • examples/react/virtualized-columns/src/main.tsx
  • examples/react/virtualized-columns/src/makeData.ts
  • examples/react/virtualized-infinite-scrolling/package.json
  • examples/react/virtualized-infinite-scrolling/src/main.tsx
  • examples/react/virtualized-infinite-scrolling/src/makeData.ts
  • examples/react/virtualized-rows-experimental/.gitignore

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Mini-ghost Mini-ghost changed the base branch from alpha to main March 10, 2026 11:34
@Mini-ghost Mini-ghost changed the title Fix/vue flexrender remount fix(vue-table): prevent unnecessary DOM mutations in FlexRender for function renderer Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant