Skip to content
New issue

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

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

Already on GitHub? # to your account

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

Open
wants to merge 12 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 3aba238.

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

☁️ Nx Cloud last updated this comment at 2025-04-08 18:35:59 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/query-async-storage-persister

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

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@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: 3aba238

# for free to join this conversation on GitHub. Already have an account? # 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