Skip to content

fix(deps): update dependency react-rx to v7 - #2139

Closed
squiggler-app[bot] wants to merge 2 commits into
mainfrom
renovate/react-rx-7.x
Closed

squiggler-app[bot] wants to merge 2 commits into
mainfrom
renovate/react-rx-7.x

Conversation

@squiggler-app

@squiggler-app squiggler-app Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
react-rx (source) ^4.2.5 → ^7.0.1 age confidence

Release Notes

sanity-io/react-rx (react-rx)

v7.0.1

Compare Source

Patch Changes
  • #​627 64f6fb1 Thanks @​breken-ai! - Fix useObservablePromise and preloadObservablePromise on Hermes (React Native), where creating the promise crashed with Maximum call stack size exceeded. Hermes does not implement Symbol.species, so .then() on a Promise subclass built another instrumented instance and recursed in the constructor. The returned ObservablePromise no longer subclasses Promise: it is a thenable that wraps a plain promise and delegates then / catch / finally to it, so derived promises are plain Promises on every engine and use(promise.then(fn)) settles instead of suspending forever. It still satisfies the ObservablePromise<T> type and works with use(), await and the Promise combinators, but instanceof Promise is now false.

v7.0.0

Compare Source

Major Changes
  • #​515 8ca73bd Thanks @​stipsan! - v7 moves every subscription out of the render phase and removes useObservableEvent. Requirements are unchanged from v6: React ^19.2, RxJS ^7.2, Node >=22.12, ESM only. The v6 to v7 migration guide has more patterns.

    useObservable and useSyncObservable require the initialValue argument. The initial value is what renders until the observable emits. Omitting it is a type error, and because JavaScript callers can bypass the types, the hooks also throw a TypeError during render when the argument is missing. Every value is valid, undefined included, but you must pass it. Functions act as initializers, like useState. To use a function as the initial value, pass an initializer that returns it. The deprecated single-argument overloads are gone.

    // v6
    const users = useObservable(users$)
    
    // v7
    const users = useObservable(users$, undefined)

    Call sites that already pass an initialValue keep their behavior. If an observable has no meaningful initial value, use useObservablePromise with use() and Suspense instead.

    useObservable and useSyncObservable never subscribe during render. The render-phase warm-up is gone. Every render, the first one and every identity change alike, shows the resolved initialValue, or the shared cache entry's last emission when the observable is already live elsewhere. The subscription starts when the component commits, and a synchronous emission replaces the initialValue right after that commit.

    • A synchronous emission no longer wins the first paint. v6 subscribed during render when no initialValue was given, so of, startWith, or a BehaviorSubject could render on the first pass. In v7 the initialValue renders first.
    • Swapping to a new observable renders the initialValue for one pass. The new observable's synchronous emission arrives after the swap commits. useObservable still never renders the previous observable's value under the new identity.
    • Observable identities must be stable across renders. Use useMemo, useState, module scope, or React Compiler memoization. An observable rebuilt on every render is torn down and re-subscribed on every render. When such a source synchronously replays a value that differs from the initialValue, every commit forces a re-render and React aborts with "Maximum update depth exceeded". The v6 warm-up let that pattern converge.
    • A synchronously erroring observable surfaces its error after commit instead of during render.
    • disabled: true now always guarantees zero subscriptions. In v6 the warm-up probe still subscribed once when no initialValue was given.
    • Server rendering renders the resolved initialValue and never subscribes. useSyncObservable can no longer hit React's "Missing getServerSnapshot" error, and a non-deterministic synchronous emission can no longer cause a hydration mismatch.

    useObservablePromise never subscribes during a mounting render. v6 started the source subscription during render unless disabled, so any render fetched as a side effect, hidden <Activity> pre-renders included. A fetch now has three triggers.

    • A non-disabled component that called the hook commits, on mount or on an <Activity> reveal.
    • A committed, visible, non-disabled consumer re-renders with a new observable identity. The hook subscribes the new source during that render, so a suspended transition can settle, retry, and commit.
    • Something calls preloadObservablePromise.

    This affects callers as follows.

    • use(useObservablePromise(obs$)) inside one component deadlocks. The component suspends on its own pending promise before the commit that would start the fetch. This was never a supported pattern and is not guarded against. Pass the promise to a child that reads it with use(), and put a <Suspense> boundary between the hook caller and that child.
    • A hidden <Activity> tree that calls the hook is paused. Nothing subscribes until the tree is revealed and its effects mount. To pre-render hidden content with data, call the hook in a visible parent and pass the promise into the hidden tree.
    • A synchronously emitting source (of, BehaviorSubject, a replayed shareReplay) resolves at the hook caller's commit, so a cold mount shows one Suspense fallback pass. Preload the observable to skip the fallback.
    • Swapping observables inside startTransition or behind useDeferredValue needs no preload. The live consumer's swap render starts the fetch, the previous content stays visible until the new data settles, and the swap then commits. preloadObservablePromise still lets a swap commit with no pending period, for example after a hover preload.
    • A live consumer that flips disabled off inside a transition starts the fetch from that render.
    • A transition abandoned after its swap render can leave a fetch nobody consumes. The entry settles into the shared cache and is evicted after ttl. Bound sources that may never settle with RxJS timeout, as you would for preloadObservablePromise.
    • On the server, preloadObservablePromise is a no-op that returns an inert pending promise. It neither subscribes nor touches the cache. Server rendering emits the Suspense fallback and the fetch starts after hydration. For React Server Components or server-only flows, fetch with await or RxJS firstValueFrom and pass the promise or value as a prop.

    useObservableEvent is removed. Replace it with useObservableSubject, available since v6.1, which returns the event stream and a stable handler that pushes into it. Then move the pipeline to where its output is consumed. A pipeline that ended in tap(setState) becomes a derived stream read with useObservable or useSyncObservable. A side-effect-only pipeline subscribes in an effect. A pipeline that only forwarded into a Subject becomes a direct next call.

    // v6
    const [value, setValue] = useState(1)
    const handleChange = useObservableEvent((value$) => value$.pipe(map(Number), tap(setValue)))
    
    // v7
    const [input$, handleChange] = useObservableSubject<string>()
    const value$ = useMemo(() => input$.pipe(map(Number)), [input$])
    const value = useSyncObservable(value$, 1)

    The use-effect-event dependency existed only for this hook and is gone. react-rx has no runtime dependencies beyond its react and rxjs peers.

v6.1.0

Compare Source

Minor Changes
  • #​585 8fed462 Thanks @​stipsan! - Add useObservableSubject, a hook that returns the two halves of an event: an observable of the values pushed into it, and a referentially stable handler that pushes them. The underlying RxJS Subject is scoped to the component instance and only its observable side is exposed.

    const [events$, handleEvent] = useObservableSubject<string>()
    const value$ = useMemo(() => events$.pipe(map(Number)), [events$])
    const value = useSyncObservable(value$, 1)
    // <input value={value} onChange={(event) => handleEvent(event.currentTarget.value)} />

    useObservableEvent is removed in v7. It is now implemented on top of useObservableSubject, which carries over to v7 unchanged, so call sites can be migrated on v6 and need no further changes after upgrading.

  • #​582 27db7b6 Thanks @​stipsan! - Deprecate the APIs that react-rx v7 removes, so editors and no-deprecated lint rules flag them ahead of the upgrade. Runtime behavior is unchanged.

    • useObservableEvent is removed in v7. Push events into a Subject you own and read the derived stream with useObservable or useSyncObservable.
    • Calling useObservable or useSyncObservable without an initialValue is removed in v7, where the argument is required. useObservable(observable$, undefined) keeps the v6 type and behavior. no-deprecated lint rules also flag uncalled references such as typeof useObservable, because they read the tags of every overload. Those are false positives. Suppress them locally.

    See the v6 to v7 migration guide.

Patch Changes
  • #​579 9010e04 Thanks @​stipsan! - initialValue initializers are now resolved once per hook instance, like a useState initializer. They previously ran on every pre-emission getSnapshot read, so an initializer returning a fresh object (useObservable(obs$, () => ({...})), and the same on useSyncObservable) gave useSyncExternalStore a new snapshot reference on every consistency check and looped until React aborted with "Maximum update depth exceeded". The resolved value is now cached per instance, which also makes the pre-emission snapshot reference stable across re-renders and fixes the equivalent hazard in useSyncObservable's server snapshot.

    As with useState, the hook reads the initialValue argument on its first render only. A different value passed on a later render is ignored until the hook remounts, where a plain value used to be re-read on every pre-emission render. Keep initializers pure. React Strict Mode calls them twice in development and keeps one result.

v6.0.1

Compare Source

Patch Changes

v6.0.0

Compare Source

Major Changes
  • #​506 4c3025c Thanks @​stipsan! - Skip the render-phase warm-up subscription in useObservable/useSyncObservable when an initialValue is provided. The warm-up only exists so sync emissions (startWith, of, …) can render on the first paint — with an initialValue there's already a value to show, so the observable is now first subscribed on commit.

    Breaking: when a sync-emitting observable is paired with an initialValue, the first paint now shows the initialValue — the sync emission arrives right after mount (previously it won on the first render, on the client and in SSR markup).

    Subscribe-time side effects (e.g. fromFetch) stay out of the render phase, disabled: true now means zero subscriptions (even when the observable is rebuilt every render), and SSR paints the initialValue without subscribing the source. Once the hook has received an emission, replacement observables on later renders are warmed during render again so components that rebuild the observable every render settle instead of looping. Omitting initialValue behaves exactly as before.

v5.1.2

Compare Source

Patch Changes
  • #​491 db29f9b Thanks @​stipsan! - perf: build with the React Compiler on oxc (oxc-transform-react, the native Rust port) instead of babel-plugin-react-compiler. The published output is unchanged — one compiler pass now handles React Compiler, TypeScript, and JSX natively, and babel is no longer part of the build or test pipeline.

v5.1.1

Compare Source

Patch Changes
  • #​479 0f7bce9 Thanks @​stipsan! - fix: make useObservable deferral identity-coherent. The observable identity and its value are now deferred as one snapshot, and when the observable identity changes (e.g. it is memoized on a document id that just changed) the hook falls back to the live value — so the previous identity's value never renders under the new one.

v5.1.0

Compare Source

Minor Changes
  • #​460 4d9b613 Thanks @​stipsan! - Add useObservablePromise and preloadObservablePromise: Suspense-ready data fetching returning a use()-compatible promise, with {disabled, ttl} options.

v5.0.1

Compare Source

Patch Changes
  • #​469 e546141 Thanks @​stipsan! - Restore the use-effect-event ponyfill so effect events see the latest values in memo and forwardRef components.

v5.0.0

Compare Source

Major Changes
  • #​459 7ed6b55 Thanks @​stipsan! - BREAKING: useObservable now defers store updates with useDeferredValue — urgent renders keep the previous value while a background render catches up. Mounts, remounts, and <Activity> reveals still render the current snapshot synchronously (no initial-value flash). SSR renders synchronous emissions (instead of always using initialValue), no longer throws when initialValue is omitted, and fails the server render on synchronously erroring observables.

    New useSyncObservable preserves v4's synchronous behavior and strict getServerSnapshot contract — use it for controlled inputs, or rename wholesale for a mechanical migration.

    See the v4 to v5 migration guide.

  • #​434 08379ed Thanks @​stipsan! - BREAKING: Require Node.js >=22.12, matching sanity's engines.node.

  • #​435 bdef45b Thanks @​stipsan! - BREAKING: Require React ^19.2 as a peer dependency. React 18 is no longer supported.

  • #​442 a89ec09 Thanks @​stipsan! - Stop publishing CommonJS builds — the package is ESM-only.

  • #​449 5e83cbe Thanks @​stipsan! - BREAKING: Require RxJS ^7.2 as a peer dependency. Import operators from 'rxjs' instead of the deprecated 'rxjs/operators' path.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about these updates again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate using a curated preset maintained by Sanity. View repository job log here


Note

Medium Risk
Major react-rx upgrade (v4→v7) changes subscription timing and hook semantics across several Studio plugins without accompanying code changes; regressions would show up as UI flicker, stale state, or infinite update loops if observables are unstable.

Overview
Bumps react-rx from ^4.2.5 to ^7.0.1 via the workspace pnpm catalog (and explicitly in @sanity/embeddings-index-ui), with pnpm-lock.yaml refreshed so every catalog consumer resolves 7.0.1. A changeset records patch releases for @sanity/embeddings-index-ui, @sanity/language-filter, @sanity/studio-secrets, and sanity-plugin-mux-input.

There are no application source edits in the diff—only manifests, lockfile churn (including dropping the old react-rx@4.2.5 tree), and release metadata. Existing plugin usage already calls useObservable with an initialValue, which aligns with v7’s required second argument, but v7 still changes when subscriptions run (post-commit vs render-phase warm-up), which can affect first paint and observable identity edge cases in Studio UI.

Reviewed by Cursor Bugbot for commit 3bd7253. Bugbot is set up for automated code reviews on this repo. Configure here.

@squiggler-app
squiggler-app Bot requested a review from a team as a code owner October 8, 2026 17:20
@squiggler-app
squiggler-app Bot requested review from stipsan and removed request for a team October 8, 2026 17:20
@vercel

vercel Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
plugins-studio Ready Ready Preview Oct 8, 2026 5:21pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
plugins-e2e-test-studio Ignored Ignored Oct 8, 2026 5:21pm UTC

Request Review

@squiggler-app
squiggler-app Bot enabled auto-merge (squash) October 8, 2026 17:20
@changeset-bot

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3bd7253

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@sanity/embeddings-index-ui Patch
@sanity/language-filter Patch
@sanity/studio-secrets Patch
sanity-plugin-mux-input Patch
sanity-plugin-internationalized-array Patch
sanity-plugin-cloudinary Patch
@sanity/cross-dataset-duplicator Patch
@sanity/personalization-plugin Patch
@sanity/sanity-plugin-async-list Patch
@sanity/document-internationalization Patch
@sanity/form-toolkit Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

✅ E2E Tests

🟢 39 passed • 🟡 5 flaky • view full report • view run

Studio: https://plugins-e2e-test-studio-k9i1z2pj4.sanity.dev

Datasets: pr-2139-chromium-37815783366, pr-2139-firefox-37815783366

@stipsan stipsan closed this Oct 8, 2026
auto-merge was automatically disabled October 8, 2026 17:39

Pull request was closed

@squiggler-app

squiggler-app Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Renovate Ignore Notification

Because you closed this PR without merging, Renovate will ignore this update. You will not get PRs for any future 7.x releases. But if you manually upgrade to 7.x then Renovate will re-enable minor and patch updates automatically.

If you accidentally closed this PR, or if you changed your mind: rename this PR to get a fresh replacement PR.

@squiggler-app
squiggler-app Bot deleted the renovate/react-rx-7.x branch October 8, 2026 18:20

This branch was successfully deployed

1 active deployment
Preview – plugins-studio — 3bd7253a Deployed Oct 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant