Skip to content

🏝️ TanStack Query DevTools for Expo/React Native! 🚀 #8846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
May 12, 2025

Conversation

LovesWorking
Copy link
Contributor

@LovesWorking LovesWorking commented Mar 23, 2025

Added events for query actions to make it easy to capture them in external dev tools on mobile devices.

These events allow my external web interface to work seamlessly across any mobile framework or device using TanStack Query.

  • Fixed online manager not syncing online status correctly.

Plugin https://github.com/LovesWorking/tanstack-query-dev-tools-expo-plugin/tree/main
Example https://github.com/LovesWorking/RN-Dev-Tools-Example/tree/master

Mac App
https://github.com/LovesWorking/rn-better-dev-tools

Preview

ios pokemon

iphone.testing.mov

Copy link

nx-cloud bot commented Mar 23, 2025

View your CI Pipeline Execution ↗ for commit daf0da1.

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 31s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 44s View ↗

☁️ Nx Cloud last updated this comment at 2025-05-12 08:56:52 UTC

@LovesWorking LovesWorking changed the title Support For Mobile Dev Tools Added action events to support mobile dev tools across platforms like React Native, Capacitor, Lynx, and others. Mar 23, 2025
@github-actions github-actions bot added the documentation Improvements or additions to documentation label Mar 24, 2025
@@ -133,6 +133,38 @@ interface ContinueAction {
type: 'continue'
}

export interface RefetchActionEvent {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think these should be here. The actions listened on the NotifyEvents are events that are dispatched from our query reducer. Consumers can listen to those to observe updates that happen in the cache. When the devtools trigger an update that will result in an internal state change, those changes will automatically be propagated.

Can you explain why those were added?

Copy link
Contributor Author

@LovesWorking LovesWorking Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other ideas to capture action events that's reliable? I added these, so I know exactly what action is pressed to forward the action to mobile. it just seemed like the easiest approach to subscribe to query events.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I don’t understand what you’re trying to do. Why do you need to capture an event that happens in the devtools? Capture it where?

Assume I understand nothing about react native and expo (which is 99% true) and try to break down for me what you’re doing. If you want to listen to events from the devtools, doing that by dispatching an event on the queryCache and then listening to that is pretty likely not the right approach

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My plugin includes a web view that runs the React Query DevTools. The challenge I’m facing is linking DevTools actions to another client—in this case, mobile. I need to know when actions like refetch, invalidate, or set online/offline are triggered inside the DevTools UI.

Right now, the web view listens for those actions and sends a message to the mobile client to trigger the same action there. This works for all DevTools actions and online state changes.

If there's a simpler way to detect which action was pressed—along with the associated query and action type—I’d be happy to update the approach.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so should we make the devtools emit those events and you’d directly listen to those?

Copy link
Contributor Author

@LovesWorking LovesWorking Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// 🌐 Web Client (DevTools in WebView) — Listening for Query Actions
// Subscribe to query changes
`const querySubscription = queryClient.getQueryCache().subscribe((event) => {
  switch (event.type) {
    case "updated":
      switch (event.action.type as QueryActions) {
        case "ACTION-REFETCH":
        case "ACTION-INVALIDATE":
        case "ACTION-TRIGGER-ERROR":
        case "ACTION-RESTORE-ERROR":
        case "ACTION-RESET":
        case "ACTION-REMOVE":
        case "ACTION-TRIGGER-LOADING":
        case "ACTION-RESTORE-LOADING":
          client.sendMessage("query-action", {
            queryHash: event.query.queryHash,
            queryKey: event.query.queryKey,
            action: event.action.type as QueryActions,
            targetDevice: selectedDeviceRef.current,
          } as QueryActionMessage);
          break;

        case "success":
          // @ts-ignore
          if (event.action.manual) {
            client.sendMessage("query-action", {
              queryHash: event.query.queryHash,
              queryKey: event.query.queryKey,
              data: event.query.state.data,
              action: "ACTION-DATA-UPDATE",
              targetDevice: selectedDeviceRef.current,
            } as QueryActionMessage);
          }
          break;
      }
  }
});


// 📱 Mobile Client — Handling Incoming Actions from Web DevTools

// Query Actions handler - Update query data, trigger errors, etc.
`const queryActionSubscription = client.addMessageListener(
  "query-action",
  (message: QueryActionMessage) => {
    const { queryHash, queryKey, data, action, targetDevice } = message;

    if (!shouldProcessMessage(targetDevice, Device.deviceName || "")) {
      return;
    }

    const activeQuery = queryClient.getQueryCache().get(queryHash);
    if (!activeQuery) {
      console.warn(`Query with hash ${queryHash} not found`);
      return;
    }

    switch (action) {
      case "ACTION-DATA-UPDATE": {
        queryClient.setQueryData(queryKey, data, {
          updatedAt: Date.now(),
        });
        break;
      }

      case "ACTION-TRIGGER-ERROR": {
        const error = new Error("Unknown error from devtools");

        const __previousQueryOptions = activeQuery.options;
        activeQuery.setState({
          status: "error",
          error,
          fetchMeta: {
            ...activeQuery.state.fetchMeta,
            // @ts-ignore
            __previousQueryOptions,
          },
        });
        break;
      }

      case "ACTION-RESTORE-ERROR": {
        queryClient.resetQueries(activeQuery);
        break;
      }

      case "ACTION-TRIGGER-LOADING": {
        const __previousQueryOptions = activeQuery.options;

        // Trigger a fetch that never resolves to simulate loading
        activeQuery.fetch({
          ...__previousQueryOptions,
          queryFn: () => new Promise(() => {}),
          gcTime: -1,
        });

        activeQuery.setState({
          data: undefined,
          status: "pending",
          fetchMeta: {
            ...activeQuery.state.fetchMeta,
            // @ts-ignore
            __previousQueryOptions,
          },
        });
        break;
      }

      case "ACTION-RESTORE-LOADING": {
        const previousState = activeQuery.state;
        const previousOptions = activeQuery.state.fetchMeta
          ? (activeQuery.state.fetchMeta as any).__previousQueryOptions
          : null;

        activeQuery.cancel({ silent: true });
        activeQuery.setState({
          ...previousState,
          fetchStatus: "idle",
          fetchMeta: null,
        });

        if (previousOptions) {
          activeQuery.fetch(previousOptions);
        }
        break;
      }

      case "ACTION-RESET": {
        queryClient.resetQueries(activeQuery);
        break;
      }

      case "ACTION-REMOVE": {
        queryClient.removeQueries(activeQuery);
        break;
      }

      case "ACTION-REFETCH": {
        activeQuery.fetch().catch(() => {});
        break;
      }

      case "ACTION-INVALIDATE": {
        queryClient.invalidateQueries(activeQuery);
        break;
      }

      case "ACTION-ONLINE-MANAGER-ONLINE": {
        onlineManager.setOnline(true);
        break;
      }

      case "ACTION-ONLINE-MANAGER-OFFLINE": {
        onlineManager.setOnline(false);
        break;
      }
    }
  }
);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so should we make the devtools emit those events and you’d directly listen to those?

Yes!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, just to be on the same page: this would mean no changes to the query-core, right? because those messages wouldn’t go through the query cache...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay great 👍

@LovesWorking LovesWorking changed the title Added action events to support mobile dev tools across platforms like React Native, Capacitor, Lynx, and others. 🏝️ TanStack Query DevTools for Expo/React Native! 🚀 Mar 26, 2025
Copy link

pkg-pr-new bot commented Mar 30, 2025

More templates

@tanstack/angular-query-devtools-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-devtools-experimental@8846

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@8846

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@8846

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@8846

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@8846

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@8846

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@8846

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@8846

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@8846

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@8846

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@8846

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@8846

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@8846

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@8846

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@8846

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@8846

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@8846

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@8846

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@8846

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@8846

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@8846

commit: daf0da1

@LovesWorking
Copy link
Contributor Author

@TkDodo Okay, I updated this to use events instead and removed the other code. Thank you!

Copy link
Collaborator

@TkDodo TkDodo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

knip says we don’t need to export those:

Copy link

codecov bot commented May 12, 2025

Codecov Report

Attention: Patch coverage is 0% with 36 lines in your changes missing coverage. Please review.

Project coverage is 15.72%. Comparing base (6a52926) to head (daf0da1).
Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main    #8846       +/-   ##
===========================================
- Coverage   44.49%   15.72%   -28.77%     
===========================================
  Files         206       34      -172     
  Lines        8150     2194     -5956     
  Branches     1829      557     -1272     
===========================================
- Hits         3626      345     -3281     
+ Misses       4081     1617     -2464     
+ Partials      443      232      -211     
Components Coverage Δ
@tanstack/angular-query-devtools-experimental ∅ <ø> (∅)
@tanstack/angular-query-experimental 85.04% <ø> (ø)
@tanstack/eslint-plugin-query ∅ <ø> (∅)
@tanstack/query-async-storage-persister ∅ <ø> (∅)
@tanstack/query-broadcast-client-experimental ∅ <ø> (∅)
@tanstack/query-codemods ∅ <ø> (∅)
@tanstack/query-core ∅ <ø> (∅)
@tanstack/query-devtools 3.56% <0.00%> (-0.05%) ⬇️
@tanstack/query-persist-client-core ∅ <ø> (∅)
@tanstack/query-sync-storage-persister ∅ <ø> (∅)
@tanstack/query-test-utils ∅ <ø> (∅)
@tanstack/react-query ∅ <ø> (∅)
@tanstack/react-query-devtools 10.00% <ø> (ø)
@tanstack/react-query-next-experimental ∅ <ø> (∅)
@tanstack/react-query-persist-client ∅ <ø> (∅)
@tanstack/solid-query ∅ <ø> (∅)
@tanstack/solid-query-devtools ∅ <ø> (∅)
@tanstack/solid-query-persist-client ∅ <ø> (∅)
@tanstack/svelte-query ∅ <ø> (∅)
@tanstack/svelte-query-devtools ∅ <ø> (∅)
@tanstack/svelte-query-persist-client ∅ <ø> (∅)
@tanstack/vue-query ∅ <ø> (∅)
@tanstack/vue-query-devtools ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@TkDodo TkDodo merged commit 696fd44 into TanStack:main May 12, 2025
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation package: query-devtools
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants