Skip to content

bug(shadcn_flutter): ContextMenu crashes if a caller switches from itemsBuilder to items at runtime #1011

Description

@ZhuchkaTriplesix

Summary

The vendored ContextMenu widget (third_party/shadcn_flutter/lib/src/components/menu/context_menu.dart) was extended to support a lazy itemsBuilder alternative to eager items (used by the grid's per-cell context menu, #983). The two modes are distinguished by whether _ContextMenuState._children is null:

@override
void initState() {
  super.initState();
  final items = widget.items;
  if (items != null) {
    _children = ValueNotifier(items);
  }
}

ValueListenable<List<MenuItem>> _resolveChildren(BuildContext context) {
  final eager = _children;
  if (eager != null) return eager;
  return ValueNotifier(widget.itemsBuilder!(context));
}

If a ContextMenu widget instance is ever rebuilt going from items: [...] (non-null) to items: null, itemsBuilder: ... (i.e. the caller switches modes across a rebuild, same widget position so the same State is reused): initState already ran with the old items and set _children to a non-null ValueNotifier, so didUpdateWidget never re-derives it, and _resolveChildren keeps returning the stale eager _children — the old, hardcoded item list — silently, forever (not a crash in this direction, but stale menu items with no way to recover other than remounting).

Going the other direction — itemsBuilder first, then rebuilt with items instead — is worse: _children was never initialized (stays null since initState only sets it when widget.items != null at first build), and if some intermediate code path expects _children to be non-null once items is provided... actually tracing further, _resolveChildren handles this gracefully today (falls through to null-check via eager == null → itemsBuilder branch), but only as long as itemsBuilder is still non-null. If a caller passes items on a later build without itemsBuilder, widget.itemsBuilder!(context) on line 614 throws Null check operator used on a null value.

Current risk

Today's single caller (_GridCell in result_grid_view.dart) always uses itemsBuilder and never items, and never switches modes on the same widget position, so this is not currently reachable. Flagging it because it's a real latent crash/staleness bug in a shared, vendored widget that other call sites could hit in the future without realizing the constraint.

Suggested fix

In didUpdateWidget, re-derive _children/mode whenever (widget.items == null) != (oldWidget.items == null) flips, instead of only reacting to items list-content changes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinguiUser interface components and widgets

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions