Breaking Changes
-
Browser storage vars (
rx.Cookie,rx.LocalStorage,rx.SessionStorage) must be declared with a storage value as the default or with a storage type annotation. Adefault_factoryon a storage var, or one returning a storage value, now fails at compile time instead of being silently ignored or treated as an ordinary var. (#7582) -
In prod mode, when
deploy_urlis unset (now the default), the backend no longer sends CORS headers, so browsers cannot read cross-origin responses, and Socket.IO accepts same-origin connections only. Withdeploy_urlset, only that origin is allowed. Previously any origin was allowed. Setcors_allowed_originsexplicitly for other setups (["*"]restores the old behavior). (#7641) -
A
setorfrozensetstate var is now aSetVarrather than anArrayVar, so it no longer type-checks as a sequence in frontend code: indexing, slicing,map,filter,join,reverse,+,*, the comparisons<,<=,>and>=, andcontainswith afieldon it are deprecated. They still compile for now with a deprecation warning and will be removed in 1.0. Call.to_array()first:State.tags[0] # deprecated State.tags.to_array()[0] ``` ([#7642](https://github.com/reflex-dev/reflex/issues/7642))
-
@rx.eventnow returns anEventHandlerinstead of the decorated function. A decorator stacked above@rx.eventis deprecated: the method still runs and registers under its own name, with a deprecation warning, until 1.0; apply other decorators below@rx.event.@rx.eventon a method of a class that is not a state keeps working when called through an instance, and on astaticmethodorclassmethodleaves it unchanged; both warn and should be removed, and calling such a method through the class (Helper.compute(obj, 3)) is not supported. A module-level function decorated with@rx.eventwhose first parameter is not a state stays a plain function and warns; drop the decorator from plain helper functions. (#7673) -
Remove the built-in starlette-admin dashboard.
rx.AdminDash(...)now raises an error linking to the admin dashboard guide, which shows how to mount starlette-admin yourself withapi_transformer. (#7739)
Deprecations
reflex.model.alembic_autogenerate,reflex.model.get_migration_historyandreflex.model.format_revision, and therx.Model.alembic_autogenerateandrx.Model.get_migration_historyclass methods, are deprecated and will be removed in 1.0.0.alembic_autogeneratenow runs through the generatedalembic/env.pyand ignores itsconnectionargument. Usealembic revision --autogenerate,alembic checkandalembic history --indicate-current, or the matchingreflex dbcommands, instead. (#7742)
Features
-
Add
rx.client_statefor client-only state. A named var (rx.client_state("", name="query")) is global: any component can read and write it, and the backend canpushandretrieveit. An unnamed var is owned by the outermost page or@rx.memothat uses it, so each@rx.memoinstance gets its own value and a page's values reset when it unmounts. Setting a var re-renders only the components that read it..settakes a value, aFunctionVar, or a lambda (count.set(lambda v: v + 1)) and replaces.set_value, which is deprecated and will be removed in 1.0.rx._x.client_stateremains available with a deprecation warning. (#6936) -
Add
rx.vars.SetVarforset[T]andfrozenset[T]state vars and literals, with set operations (contains,union,intersection,issubset, ...) andto_array()for list operations andrx.foreach. Array operations used directly on a set var are deprecated. (#7642) -
Add development-time detection for event handlers that block the asyncio event loop. (#7672)
-
Event handlers are awaitable:
await State.handler(args)inside another event handler runs it right away on the same locked state and returns its result, so a handler may return a value to the backend code awaiting it (returned events still chain, a returned coroutine still raises, and return values are not sent to the frontend yet).@rx.eventreturns theEventHandleritself, andself.handler()keeps calling the plain method as before.@rx.event def add(self, amount: int) -> int: self.total += amount return self.total @rx.event async def add_twice(self, amount: int): first = await type(self).add(amount) yield rx.toast(f"total is now {await type(self).add(amount)}") ``` ([#7673](https://github.com/reflex-dev/reflex/issues/7673))
-
The
dbextra now installsaiosqlite, the async SQLite driver, soreflex[db]works with SQLAlchemy's async engine and sessions on the default SQLite database without a separate install. (#7679) -
Add the experimental
auto_memoizeoption (REFLEX_AUTO_MEMOIZE). Setauto_memoize=Falsetogether withreact_compiler=Trueto let React Compiler memoize whole pages instead of Reflex's generated memo wrappers, which also shortens compilation. Disabling both options logs a warning. Components with lifecycle events or hooks of their own keep a wrapper, soon_mountandon_unmountstill follow theirrx.condbranch. (#7694) -
reflex db initnow runsalembic initand installs analembic/env.pythat loads the app and readsdb_urlfrom rxconfig.py, andreflex db migrate,makemigrationsandstatusare thin wrappers overalembic upgrade head,alembic revision --autogenerateandalembic history --indicate-current. Thealembiccommand line works on the same environment, soalembic downgrade -1needs no extra setup, andalembic revision --autogenerateandalembic checkevaluate the pages asreflex db makemigrationsdoes, so a model that only a page imports is compared too. In apps initialized by an earlier release, the nextreflex dbcommand ormigrate()call replaces the stockenv.pywith the current template, keeping the previous file asenv.oldand the existing revisions;migrate()logs a warning when it does so. (#7742) -
reflex db initwritesalembic/env.pyfrom the template when the file is missing, so anenv.pyedited by hand can be replaced by moving it aside, and it no longer prints alembic's list of generated template files. (#7742) -
Allow redis-py 8. Reflex's redis clients disable redis-py 8's new default socket timeout, as redis-py 7 had none; on Python 3.11 that timeout loses task cancellations and can hang shutdown. (#7757)
Bug Fixes
- Preserve state changes made by foreground event handlers that raise or are cancelled when using Redis, including updates already yielded to the client before a superseding event, matching the other state managers. (#7412)
- Fixed the redis state manager stalling a token's events until its lock expired after an event was cancelled (for example by
supersedes) or failed, or after a lock release notification was lost. WithREFLEX_OPLOCK_ENABLED, events queued on a lease no longer lose their updates when together they outlast the lock expiration, and events waiting on other instances are no longer passed over by the next lease. (#7534) - Make production frontend builds reproducible: exporting the same app twice now gives identical files, so a deploy no longer renames scripts that did not change. (#7564)
- Backend workers now load
rxconfig.pybefore they import the app, sostate_auto_setters=Truegeneratesset_<var>setters in workers started with spawn (Windows, macOS,REFLEX_STRICT_HOT_RELOAD,REFLEX_BACKEND_START_METHOD=spawn). (#7590) - Match page routes case-insensitively on the backend, as React Router does on the frontend, so
on_loadevents androuter.page.pathresolve for a URL like/Aboutinstead of falling back to the 404 page. (#7599) - After a hot reload in dev mode, cached vars restored by the disk or Redis state manager are recomputed with the new code instead of showing values from before the reload. State fields keep their values. (#7616)
- Fixed a polling-transport stall where an event larger than
REFLEX_SOCKET_MAX_HTTP_BUFFER_SIZEleft the page unable to reconnect: the backend now reads the oversized request body before it rejects it, so the browser can reconnect. (#7621) - Apps passed to
api_transformernow get ASGI lifespan events, so thelifespan(andon_startup/on_shutdownhandlers) of a FastAPI or Starlette transformer run, and state the lifespan yields is available onrequest.state. Events are forwarded through callable transformers as the ASGI spec describes; apps you mount on a transformer yourself still get none. If you worked around this by also registering the transformer's lifespan withregister_lifespan_task, or by finding the transformer through thestarlette_applifespan task argument, remove the workaround: the first now runs the lifespan twice, andstarlette_appis now theStarletteapp serving Reflex's own routes. (#7624) - Synced
LocalStoragevars that share one storagenamenow update in the tab that writes, not only in other tabs. (#7637) reflex runnow stops the whole frontend process tree on SIGINT or SIGTERM, including a dev server left behind bynpm run, a repeat right after the first signal no longer interrupts that cleanup, and a later Ctrl-C forces the exit when the cleanup is stuck. An exit the CLI asked for is no longer reported as a frontend failure. (#7650)app.add_page(...)now raisesBackendVarFormatErrorwhentitle,descriptionorimageis a backend var such asState._title, instead of failing later at compile time with an unrelated error. (#7651)reflex run --frontend-onlyandreflex run --backend-onlyno longer exit with "Cannot specify --backend-port/--frontend-port" whenrxconfig.pysetsfrontend_portorbackend_port. Only a port flag given on the command line for the side that does not run is an error. (#7693)- A background task whose
async withon a state reached throughget_statewas cancelled, or failed while loading that state, no longer leaves the client's state locked, which hung every later event for that client and the server's shutdown. (#7738) reflex db migrate,reflex db makemigrationsandreflex db statusnow report thatdb_urlis not configured in rxconfig.py instead of failing inside alembic, and exit with status 1 when there is no database to migrate or it is not initialized. (#7742)- The schema check that runs after
reflex db migrateand at app startup now evaluates the pages first, so a model that only a page imports no longer triggers the "Detected database schema changes" warning. (#7742) reflex db initandreflex db makemigrationsnow evaluate every page before comparing the models, so a model that a page imports lazily is no longer missed or dropped from the generated revision after a previousreflex runhas written the stateful-pages marker. (#7742)- The Redis state manager now starts its lock subscriber on the event loop that serves the app, not the one running when the app is built. This fixes
RuntimeError: ... attached to a different loopat backend shutdown, and the occasionalRedis error deleting tokenfailure, inAppHarnesstests that use Playwright's sync API with Redis. (#7751) - Fix hooks of a component inside
rx.foreach, such ason_mount,on_unmount, and hooks fromadd_hooks. A hook that used the loop variable crashed the page, and other hooks ran once for the whole list. Each item now runs its own hooks. A loop inside an@rx.memofunction is not covered yet. (#7793)
Performance
- Lazy bundled libraries now load only the modules required by each dynamic component instead of loading every registered optional library at once. (#7276)
- The backend no longer wraps the app in an extra
Starletteapplication to run lifespan tasks, which removes a routing layer from every request. As a result, calling therx.Appinstance returns a plain ASGI app rather than aStarletteinstance. (#7624)