
This tool works with both Expo and React Native CLI projects. Just install and go.
Save a named snapshot of registered client-state sources and restore it when repeating a test. Sources can include storage, Redux, Zustand, Jotai, React Query, and the current Expo Router URL.
Check the Sources strip before capture. A snapshot covers the sources available to Buoy; it does not include backend state, component-local state, or in-flight requests.
This interactive demo uses sample snapshots of configured stores, query data and navigation. A real restore point covers only the adapters and values captured by your app.
npm install @buoy-gg/time-machineThat's it — auto-discovery finds the installed package and the TIME MACHINE tool appears in your floating menu:
import { FloatingDevTools } from "@buoy-gg/core";
export default function App() {
return (
<>
<YourApp />
<FloatingDevTools />
</>
);
}
import { FloatingDevTools } from "@buoy-gg/core";
export default function App() {
return (
<>
<YourApp />
<FloatingDevTools />
</>
);
}
Each state source is captured through the Buoy tool that already watches it — install the ones you use (@buoy-gg/storage, @buoy-gg/redux, @buoy-gg/zustand, @buoy-gg/jotai, @buoy-gg/react-query) and they register as snapshot sources automatically. The tool's Sources strip shows you exactly which sources can capture and restore, and why when one can't — including Route, which lights up on any Expo Router app and needs no extra package.
/checkout · 14:32), and you can rename it from the row later. Captured client state persists on-device and survives app restarts and JS reloads./checkout/payment, query string and all), and "Return to this screen" sits with the other changes in the restore preview. It's on by default and it's a property of the restore point, so you answer once rather than on every restore. The app navigates as soon as the state lands — or right after the reload, when you restored in reload mode. Needs Expo Router; without it snapshots carry no route and the checkbox doesn't appear.useState that reads storage only when it mounts, such as a cart provider hydrated from AsyncStorage. A live restore puts the storage key back but that screen never re-reads it; a restore that reloads makes it rebuild from the restored storage. + asks for a name (leave it empty for the screen and time), captures a new point right there and arms it; the chevron beside the name switches to another point. The strip comes back after a reload, drags anywhere, and hides at the screen edge from its grip. Close it with the × when the loop is done; nothing else changes.time_machine_action): capture → drive the flow → restore → repeat is one tool call per step.| Source | Restore mechanism |
|---|---|
| AsyncStorage | Diff: keys missing from the snapshot are removed, snapshot entries bulk-written. Buoy's own keys are never touched. |
| MMKV | Per registered instance, per-key diff. Read-only instances and ArrayBuffer values are skipped (and reported). |
| SecureStore | Registered keys written back with their original options. Biometric-protected keys are never read or written. |
| Redux | A single full-state jump through Buoy's reducer wrapper, verified after dispatch. Works with the zero-config enhancer, or wrap your root reducer in withBuoyDevTools() when using the middleware. |
| Zustand | Full setState(state, true) replace — with your store's action functions re-grafted from the live store first, so useStore(s => s.increment) keeps working. |
| Jotai | Every watched, writable atom is set individually. Read-only atoms are captured for inspection and left to recompute; writable derived atoms may be restored through their write function. |
| React Query | Per-query diff against the live cache: existing queries get their state set (observers stay attached, with their real queryFns), missing ones are rebuilt, extras removed. Query data is captured in full — snapshots live on-device, so nothing is trimmed for size — while error objects keep only their name and message. Mutations aren't replayable, so the mutation cache is cleared. |
| Route | Opt-in per restore: router.navigate() to the captured URL — after the state is applied, or on the next boot when the restore reloads. |
Limits: this is client state — your backend doesn't time-travel, so a restored cart is only as valid as the server allows. Component-local useState and in-flight requests aren't captured. The route restores as a URL (pathname + search params) — the back stack it sat on, and params you passed imperatively as objects rather than in the URL, do not. Sources that can't fully restore say so up front, in the tool.
Install @buoy-gg/time-machine and capture a restore point — it snapshots device storage (AsyncStorage, MMKV, SecureStore), Redux, Zustand, Jotai, and the React Query cache together, and restores the whole set in one tap. Set your test state up once and jump back to it every iteration.
Only if you want it to. Live restore swaps state in place — stores are replaced, the query cache is diffed query-by-query so mounted components keep their subscriptions, and the app stays exactly where it is. Restore + reload writes storage back and reloads the JS bundle so in-memory stores rebuild themselves.
Tap Action bar in the Time Machine header, or swipe a restore point and choose Bar. The tool minimizes and a strip stays over the app with that point armed. Each tap on its restore button puts that state back; a long press puts the state back without leaving the screen you are on, and the bar keeps a "Before last restore" copy so you can undo a mis-tap within ten seconds. The strip's + asks for a name, captures a new point from wherever you are and arms it, so moving the checkpoint forward takes a name and a tap.
Yes — they persist on-device through restarts and JS reloads, and each one also records the route it was captured on so you can return to the screen as well as the state.
Register this package’s /web namespace in FloatingDevTools modules to use its shared panels and actions in a browser app. The browser build is available in this checkout and has not been published yet. See the web setup guide for registration, dependencies, and browser boundaries.