payloadcms / payloadcms/payload

`WhereBuilder/Condition/Select`: "Maximum update depth exceeded" when filter value/operator multiplicity mismatch (sibling to already-fixed #11080 Relationship filter bug)

Open
#17,248 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

Describe the Bug

packages/ui/src/elements/WhereBuilder/Condition/Select/index.tsx contains a self-correcting useEffect that can enter an infinite render loop, crashing the list view with:

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

We caught this once in production via Sentry (a real user session, single occurrence) on Payload 3.85.1, then traced it to source. The compiled component (React Compiler output, but the logic is unchanged from source) contains:

// WhereBuilder/Condition/Select/index.js
React.useEffect(() => {
  if (!isMulti && Array.isArray(value)) {
    onChange(value[0])
  }
}, [isMulti, onChange, value])

If value is an array while isMulti is false (i.e. the condition's operator no longer expects an array, but the value hasn't been normalized to a scalar yet), this effect calls onChange(value[0]). If the parent's updateCondition handler round-trips back a value that is still array-shaped on the next render (e.g. due to how the condition's operator/value pair gets serialized to/from the URL query string, or a race between the parent's own value-reset logic and this effect), the effect re-fires indefinitely.

This is the same class of bug that was already fixed in the sibling WhereBuilder/Condition/Relationship component via #11080 ("fix(ui): relationship filter renders stale values when changing fields"), which explicitly called out:

The RelationshipFilter component was previously relying on a useEffect that had a callback in its dependencies. This was causing the effect to run uncontrollably using old references. To avoid this, we use the new useEffectEvent approach...

That fix does not appear to have been applied to the plain select-field Condition/Select component, which still uses a plain useEffect for the equivalent self-correction logic.

To Reproduce

We were not able to reliably reproduce this interactively (single production occurrence, not seen again), but the mechanism suggests:

  1. In the list view, add a filter on any select-type field.
  2. Choose a multi-value operator (is in / is not in) and select 2+ options, so value becomes an array.
  3. Switch the operator to a single-value operator (equals, etc.) through a path that does not go through Condition's own handleOperatorChange guard — e.g. browser back/forward navigation restoring a stale where query string, or manually editing the URL — so that operator and value arrive at the Select component already mismatched on mount/update, rather than being normalized by handleOperatorChange first.
  4. Observe the infinite "Maximum update depth exceeded" loop.

(Normal UI interaction — clicking the operator dropdown — does not trigger this, because Condition's handleOperatorChange already resets the value when the type doesn't match the new operator. The bug requires the mismatched state to arrive without going through that handler.)

Expected behavior

Switching a select filter between single- and multi-value operators, however the mismatched state arises, should not crash the list view.

Environment

  • Payload: 3.85.1
  • Adapter: @payloadcms/db-postgres 3.85.1
  • Next.js: 16.2.9
  • React: 19.1.0
  • Node: 20.20.2

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/ui/src/elements/WhereBuilder/Condition/Select/index.tsx and trace the self-correcting effect, its onChange callback, and the value/operator inputs. Compare the equivalent logic in WhereBuilder/Condition/Relationship and the approach described in #11080. Done means a mismatched select value and operator no longer causes a repeated update or crashes the list view.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, react, typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.