Skip to content

Library item detail view white-screens (React error #310) — Rules-of-Hooks violation in LibraryView.tsx #17

Description

@MiggiJiggy999

Cockpit version: 1.2.1 (scaffold v4.1.1)
Environment: Windows 11, Node v24, Chromium browser. Reproducible regardless of OS/browser (it is a client-side logic bug).

Summary

Opening any populated library item detail page (#/library/<lib>/<slug>) crashes the entire SPA to a blank white screen. Console shows:

Error: Minified React error #310 (Rendered more hooks than during the previous render)
    at ... useMemo ...
    at LibraryItemView (assets/LibraryView-*.js)

The bug is latent in the shipped product: the two bundled libraries (Recipes, Films & Series) ship empty, so the item detail view is never exercised with real content. It surfaces the moment any library has at least one item (I hit it with a community Business-Knowledge library that registers BKM/ via the standard library_registry mechanism).

Root cause

In web/src/views/LibraryView.tsx, component LibraryItemView, the metaRows useMemo is declared after the early returns:

function LibraryItemView({ lib, itemSlug }) {
  const { data, loading, error } = useFetch(...);
  const topRef = useRef(...);
  useEffect(() => {...}, [lib, itemSlug]);

  if (loading) return <ViewSkeleton />;               // early return
  if (error)   return <div>...</div>;                  // early return
  if (!data || !data.found || !data.item) { return ... } // early return

  const item = data.item;
  const metaRows = useMemo(() => {...}, [item]);       // <-- hook AFTER conditional returns
  ...
}

On the first (loading) render an early return fires, so useMemo is skipped. On the next (data-loaded) render it runs — the hook count changes between renders, violating the Rules of Hooks → React #310 → unrecoverable render error → blank app.

Fix

Move the useMemo above all early returns so it runs on every render, computing from the possibly-undefined data?.item:

function LibraryItemView({ lib, itemSlug }) {
  const { data, loading, error } = useFetch(...);
  const topRef = useRef(...);
  useEffect(() => {...}, [lib, itemSlug]);

  const metaRows = useMemo<Array<[string, string]>>(() => {
    const rows: Array<[string, string]> = [];
    const it = data?.item;
    if (!it) return rows;
    for (const [k, v] of Object.entries(it)) {
      if (k === 'body' || k === 'raw_frontmatter' || k === 'id' || k === 'slug' || k === 'file_path' || k === 'title') continue;
      if (Array.isArray(v)) { if (v.length) rows.push([humanLabel(k), v.join(', ')]); continue; }
      const s = scalarText(v as LibraryItem[string]);
      if (s) rows.push([humanLabel(k), s]);
    }
    return rows;
  }, [data]);

  if (loading) return <ViewSkeleton />;
  if (error)   return <div>...</div>;
  if (!data || !data.found || !data.item) { return ... }

  const item = data.item;
  // ... unchanged JSX that maps metaRows ...
}

Verified: with this change a fresh load of a populated library item renders the full detail page (body + Details panel) with zero console errors.

Suggested guards

  • Add an error boundary around the route subtree so a single component error degrades to an inline message instead of blanking the whole app.
  • Enable the react-hooks/rules-of-hooks ESLint rule in CI — it catches this class of bug at build time.

Repro steps

  1. Register any library with ≥1 item (or add one recipe/film note so recipes/movies is non-empty).
  2. Navigate to that library and click an item card.
  3. Observe the blank screen; console shows React #310 from LibraryItemView.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions