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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

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 5cae570.

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

☁️ Nx Cloud last updated this comment at 2025-04-18 16:22:37 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 👍

Copy link
Collaborator

Choose a reason for hiding this comment

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

@ardeora can you have a look here please?

Copy link
Contributor Author

@LovesWorking LovesWorking Apr 8, 2025

Choose a reason for hiding this comment

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

@ardeora @TkDodo Anything else I would need to do for this PR? Thanks.

@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: 5cae570

Comment on lines +183 to +190
| TriggerErrorActionEvent
| RestoreErrorActionEvent
| RefetchActionEvent
| RestoreLoadingActionEvent
| RemoveActionEvent
| TriggerLoadingActionEvent
| ResetActionEvent
| InvalidateActionEvent
Copy link
Collaborator

Choose a reason for hiding this comment

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

even though we’re not doing anything with these action in the query-core, the type is still widened, which means users will likely start to listen to these events even though they cannot be received except from the devtools.

okay, just to be on the same page: this would mean no changes to the query-core, right?

I don’t think we’re talking about the same things till, because no changes to the query-core means the package isn’t touched, but that’s not the case right now.

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

the devtools now emit the events, but they emit them to the QueryCache still. The only reason they do this is, I think, because the only thing you can subscribe to is the QueryCache.

I think what I wanted to get to is that you somehow subscribe to the devtools themselves and listen to their events in addition to subscribing to the cache; I don’t really want the events to be the same thing and go through the 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, that makes much more sense. That was my original plan as well... I’m not sure why I changed it. Probably too much Red Bull these past few weeks 😅. I’ll work on that, thank you!!

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-core package: query-devtools
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants