Skip to content

[2.x] fix: stop ItemList.toArray() coercing null content to an empty object - #5000

Merged
imorland merged 1 commit into
2.xfrom
im/fix-4998-itemlist-null-content
Aug 27, 2026
Merged

imorland merged 1 commit into
2.xfrom
im/fix-4998-itemlist-null-content

Conversation

@imorland

Copy link
Copy Markdown
Member

Fixes #4998

Changes proposed in this pull request:

ItemList.toArray() attaches itemName to each item by wrapping its content in a Proxy. Content that isn't already an object gets boxed first, with Object(item.content) — and Object(null) is {}. So an item added as null came out of toArray() as an empty object. Mithril treats a plain object child as an already-normalized vnode, reads vnode.tag.view in initComponent, and throws TypeError: Cannot read properties of undefined (reading 'view').

PageStructure is where this surfaces. It correctly guards the optional attribute:

items.add('sidebar', (this.attrs.sidebar && this.attrs.sidebar()) || null, 100);

so omitting sidebar puts null into the list, and the whole page fails to render. sidebar is documented as optional and typed as optional, so this is documented usage that crashes.

PageStructure.tsx:86 is the only place in core and the bundled extensions that puts a possibly-null value into an ItemList, but the trap is in the shared utility: any extension writing items.add('foo', condition ? <Foo /> : null) hits the same crash.

Null and undefined content now skip the boxing path and pass through as-is. Mithril renders both as nothing, which is what the sidebar-less layout wants.

The check is explicitly against null/undefined rather than falsiness. A falsy check would box 0, false and '' into {} too, breaking itemName on them; there's a parameterised test pinning that down.

This isn't a regression. The coercion is byte-identical in 1.8.19 — 2.x just shipped the first core component that feeds a null into an ItemList.

Reviewers should focus on:

  • Whether anything is entitled to rely on toArray() never yielding null. Two call sites read a property straight off the result — Application.tsx:356 (initializer.itemName) and package-manager's QueueSection.tsx:186 (item.label). Both now throw on property access where they previously got the {} proxy, but both then call initializer(this) / content(task), so a null item already crashed a line later. Null was never valid in either list. I think that's the full extent of the behaviour change, and it's worth a second pair of eyes.
  • Whether the empty <div class="Page-sidebar"> should still render when no sidebar is given. This PR leaves it, since removing it changes the grid for anything styling that element. Happy to drop it if that's preferred, but it felt out of scope for a crash fix during RC.

Screenshot

Not applicable — no visual change. The before/after is a page that renders versus a page that doesn't.

Necessity

  • Has the problem that is being solved here been clearly explained? — <PageStructure /> without a sidebar attribute renders nothing and throws, per [2.x] <PageStructure /> crashes when the sidebar attribute is omitted #4998.
  • If applicable, have various options for solving this problem been considered? — the alternative was fixing PageStructure alone (skip adding the item when no sidebar is given). That fixes the reported symptom but leaves the same landmine for any extension adding a conditional null item, so I fixed the utility instead.
  • For core PRs, does this need to be in core, or could it be in an extension? — ItemList is core.
  • Are we willing to maintain this for years / potentially forever? — it removes a special case rather than adding one.

Confirmed

  • Frontend changes: tested on a local Flarum installation. — not verified in a running forum; I reproduced and verified it in jest instead (see below). Worth a reviewer confirming against the reporter's repro extension.
  • Frontend changes: tests are green (run yarn test in js/). — core: 80 suites / 460 tests pass, check-typings clean. Also ran the bundled extension frontend suites, which pull core's tests in: embed 475, mentions 463, tags 469, realtime 466, messages 460 — all pass.
  • Frontend changes: tests have been added, or are not appropriate here. — a new ItemList unit suite (core had none) covering null and undefined content, plus the boxing/ordering behaviour that must not change; and a new PageStructure integration test rendering with a sidebar, without one, and with no attributes at all. The two PageStructure cases fail on 2.x with the exact stack from the issue.
  • Backend changes: tests are green (run composer test). — no backend changes.
  • Backend changes: tests have been added, or are not appropriate here. — n/a.
  • Where applicable, changes are suitable for all supported database drivers (MySQL, MariaDB, PostgreSQL, SQLite). — no database involvement.
  • Core developer confirmed locally this works as intended.
  • The description above is written by me and describes what this pull request actually does.

Required changes:

  • Related documentation PR: to follow separately. The docs list className as optional while the interface types it as required, and pane isn't documented at all — both noticed while checking this report, neither touched here.

toArray() boxed every non-object item with Object() so the itemName proxy
could be attached. Object(null) is {}, so a null item became an empty
object, which Mithril treats as a vnode with an undefined tag and throws
"Cannot read properties of undefined (reading 'view')" on render.

PageStructure hits this when the sidebar attribute is omitted, taking the
whole page down. Any extension adding a conditional item as null hits it
too.

Null and undefined content now bypass the boxing path. Falsy primitives
are still boxed, so itemName stays readable on 0, false and ''.
@imorland
imorland requested a review from a team as a code owner August 27, 2026 17:00
@imorland imorland added this to the 2.0.0-rc.8 milestone Aug 27, 2026
@imorland
imorland merged commit 69f1a5a into 2.x Aug 27, 2026
29 checks passed
@imorland
imorland deleted the im/fix-4998-itemlist-null-content branch August 27, 2026 17:13
imorland added a commit that referenced this pull request Aug 27, 2026
imorland added a commit that referenced this pull request Aug 27, 2026
imorland added a commit that referenced this pull request Aug 27, 2026
* chore: prep rc.8

* docs: add #5000 to the rc.8 changelog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[2.x] <PageStructure /> crashes when the sidebar attribute is omitted

1 participant