Skip to content

perf: eliminate JSON.stringify in ReduxTab render hot pathΒ #32

@hoainho

Description

@hoainho

🌟 Before claiming this issue

Two quick steps before you open a PR:

  1. ⭐ Star the repository β€” low-friction signal that you'll follow through
  2. πŸ’¬ Comment I'll take this (or similar) below β€” prevents two contributors racing on the same issue

Full policy: CONTRIBUTING.md β†’ How to claim. If a claim is older than 7 days with no PR, you can reclaim it politely.


What

renderValue in src/panel/tabs/ReduxTab.tsx is called recursively for every visible node in the Redux state tree. Two hot-path issues:

Issue 1 β€” Line 298: JSON.stringify for path equality check

const isEditing = JSON.stringify(editingState.path) === JSON.stringify(pathArray);

This serializes two arrays on every recursive call. With 50+ visible nodes, that's 100+ JSON.stringify calls per render.

Issue 2 β€” Line 444: search filter inside renderValue

JSON.stringify(obj[key]).toLowerCase().includes(searchQuery.toLowerCase())

This stringifies the full value of every root-level key on every keystroke in the search box.

How to fix

Fix 1: Use a delimiter join instead of JSON.stringify

// BEFORE (line 298)
const isEditing = JSON.stringify(editingState.path) === JSON.stringify(pathArray);

// AFTER β€” \0 is safe because path segments are object keys or array indices
const isEditing = editingState?.path.join('\0') === pathArray.join('\0');

Or even better, lift the editing path into a precomputed string at the top of the render:

const editingPathKey = editingState?.path.join('\0') ?? null;
// inside renderValue:
const isEditing = editingPathKey === pathArray.join('\0');

Fix 2: Hoist search filter into useMemo

// AT TOP of the component, near the existing useState calls:
const filteredRootKeys = useMemo(() => {
  if (!searchQuery) return Object.keys(state as object);
  const q = searchQuery.toLowerCase();
  return Object.keys(state as object).filter(key =>
    key.toLowerCase().includes(q) ||
    JSON.stringify(state[key]).toLowerCase().includes(q)
  );
}, [state, searchQuery]);

// THEN β€” use filteredRootKeys instead of recomputing inside renderValue

This way, the filter runs once per keystroke instead of once per visible node.

Verify

  1. Open a real React app with Redux DevTools.
  2. Connect React Debugger, expand the state tree.
  3. Open Chrome DevTools Performance tab, record a 5-second trace while typing in the Redux search.
  4. Before fix: flamegraph shows JSON.stringify taking ~5-20ms per keystroke.
  5. After fix: JSON.stringify calls drop dramatically; keystroke latency falls.

Acceptance criteria

  • JSON.stringify(editingState.path) removed from renderValue
  • Search filter hoisted into useMemo
  • Existing tests pass
  • PR description includes Performance trace before/after

Scope: S (~15 lines changed) | Effort: ~1h including perf measurement | Risk: Lane Tiny

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestgood first issueGood for newcomersperfPerformance improvements

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions