Persisting Component State in Reflex with localStorage
Learn how to automatically save Reflex component state to browser localStorage using the persist flag, see a working counter example, and understand the storage limits and client‑only constraints.
12 Aug 2025, 01:50 UTC

Problem: Losing UI state on refresh
When a user interacts with a Reflex app—typing in a form, toggling a switch, or clicking a counter—their changes disappear as soon as the page reloads. Re‑entering that information is frustrating and can break workflows that rely on temporary client‑side data.
How Reflex persists state
Reflex offers a persist=True flag (or the @rx.persist decorator) on any rx.Var inside a State class. When the flag is set, the framework serializes the variable’s value to JSON and writes it to localStorage under a namespaced key like reflex::. On each page load, before the first render, Reflex reads that key, deserializes the JSON, and hydrates the variable so the UI starts with the last saved value.
Worked example: a persisting counter
- Create a new Reflex project (run
reflex initin a terminal with appropriate permissions). - Replace the generated
state.pywith the following code:
import reflex as rx
class CounterState(rx.State):
count: rx.Var[int] = rx.Var(0, persist=True)
def increment(self):
self.count = self.count + 1
Then update the default page (index.py) to bind the variable and the handler:
def index():
return rx.vstack(
rx.heading("Counter demo"),
rx.text("Count: ", CounterState.count),
rx.button("Increment", on_click=CounterState.increment),
spacing="4",
align_items="center",
padding="2rem",
)
Run the app with reflex run. Click the button several times, then manually refresh the page. The displayed number should remain at the last clicked value. Open the browser’s developer tools, go to the Application → localStorage panel for the app’s origin, and you will see a key similar to reflex:CounterState:count holding a JSON number (e.g., 5).
Trade‑offs and limitations
- Storage size:
localStorageis limited to roughly 5 MB per origin. Storing large objects or many variables can quickly exceed this quota, causing silent failures or quota‑exceeded errors. - Serializability: Only JSON‑serializable values (numbers, strings, booleans, arrays, plain objects) can be persisted. Custom class instances, functions, or DOM nodes must be converted to plain data before assignment.
- Client‑only: Persistence happens in the browser. Server‑side rendering or pre‑rendered pages will not rehydrate from
localStorage, which may produce a flash of stale content on the first paint if the server renders a different default. - Tab isolation: Changes in one tab are not automatically propagated to other tabs unless you add extra synchronization logic (e.g., a
storageevent listener).
Actionable closing
If your UI needs to remember simple user‑entered data across reloads, enable persist=True on the relevant rx.Var fields. Start with a small counter or form field, verify the localStorage entry, and monitor storage usage as you add more persisted variables. For larger or shared state, consider supplementing Reflex’s built‑in persistence with a server‑backed store or a client‑side synchronization mechanism.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.