jeecgboot/JeecgBoot · error

useTableContext must be used after createTableContext

Error message

useTableContext must be used after createTableContext

What it means

useTableContext uses Vue's provide/inject with a dynamic Symbol key. The parent table calls createTableContext (which provides a Symbol under '__BASIC_TABLE_CONTEXT_KEY__'), and child cells/features call useTableContext to inject the table instance. If a child invokes useTableContext but no ancestor has called createTableContext, the inject returns undefined and the guard throws. This enforces that context-only features are never used detached from a table.

Source

Thrown at jeecgboot-vue3/src/components/Table/src/hooks/useTableContext.ts:30

};

type RetInstance = Omit<Instance, 'getBindValues'> & {
  getBindValues: ComputedRef<BasicTableProps>;
};

export function createTableContext(instance: Instance) {
  // 每次创建 context 时都生成新的唯一 Symbol
  const key = Symbol(`basic-table-${++tableIdCounter}`);
  provide(key, instance);
  // 同时提供一个内部标记,让子组件能获取到这个 key
  provide('__BASIC_TABLE_CONTEXT_KEY__', key);
}

export function useTableContext(): RetInstance {
  // 从最近的父组件获取 context key
  const key = inject<symbol>('__BASIC_TABLE_CONTEXT_KEY__');
  if (!key) {
    throw new Error('useTableContext must be used after createTableContext');
  }
  return inject(key) as RetInstance;
}

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Ensure the component calling useTableContext is always a descendant of a BasicTable/JVxeTable that calls createTableContext.
  2. In tests/stories, wrap the component in a minimal table or mock the provide('__BASIC_TABLE_CONTEXT_KEY__') call.
  3. Add a v-if guard so the child only renders when inside a table (pass an `inTable` prop).
  4. If you need table-like features standalone, refactor to pass the instance via props instead of inject.

Example fix

// before — cell used outside a table
<MyCustomCell />  <!-- calls useTableContext, throws -->

// after — wrap in a table provider, or guard
<BasicTable>
  <MyCustomCell />
</BasicTable>
Defensive patterns

Strategy: type-guard

Validate before calling

// Check inject availability before using context
import { inject } from 'vue';
function useTableContextOrNull() {
  const key = inject<symbol>('__BASIC_TABLE_CONTEXT_KEY__');
  if (!key) return null;
  return inject(key);
}

Type guard

import { inject } from 'vue';
function isInsideTable(): boolean {
  return !!inject<symbol>('__BASIC_TABLE_CONTEXT_KEY__');
}

Try / catch

try {
  const ctx = useTableContext();
} catch (e) {
  // not inside a table; render a fallback or skip
}

Prevention

When it happens

Trigger: A JVxeTable cell component or a feature hook (e.g. a toolbar or pagination sub-component) rendered outside of a <JVxeTable> or <BasicTable> wrapper. Also triggered when a cell is rendered in a preview/storybook environment without the table provider, or when the table's createTableContext is conditionally skipped.

Common situations: Reusing a table cell component in a standalone form or grid; rendering table sub-components in isolation for testing; refactoring that moves a cell outside the table slot; conditional rendering that skips the table wrapper but still renders children.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/d6b1489f19ad2767. Report an issue: GitHub.