reduxjs / redux-devtools

DevTools for Redux with hot reloading, action replay, and customizable UI
http://youtube.com/watch?v=xsSnOQynTHs
MIT License
14.01k stars 1.16k forks source link

[Question] Is time travel supported for `@redux-devtools/remote`? IOW: Is `@redux-devtools/remote` read-only? #1340

Open msabramo opened 1 year ago

msabramo commented 1 year ago

Thank you for a great tool!

The app I work on is a React app that can work in a desktop browser or in a web view in an iOS or Android app (we use regular React though; not React Native).

When I run the app in the Chrome browser and use the Redux DevTools Chrome extension, I can jump to previous states and the state of the Redux store in the app in the browser will update appropriately.

But when the app with the web view is running on an iPhone and connecting to a remote redux-devtools server on my Mac laptop, then I can observe actions and state changes in the app and can dispatch actions that affect the app's state with Redux DevTools, but I cannot jump back to a previous state in Redux DevTools and have the app state update appropriately.

Some more info on what I'm doing:

On my Mac:

redux-devtools --open=electron --hostname=localhost --port=8000

to run the server on my laptop.

And in the app code I have:

import { configureStore } from '@reduxjs/toolkit';
import { devToolsEnhancer } from '@redux-devtools/remote';

...
export const store = configureStore({
  reducer: {
    ...
  },

  devTools: false,
  enhancers: [
    devToolsEnhancer({
      realtime: true,
      name: 'DX',
      hostname: '192.168.86.54', // IP address of machine where redux-devtools server is running
      port: 8000,
      trace: true,
      traceLimit: 10,
    }),
  ],
});

I can use Redux DevTools to browse the app's actions and state, but if I jump to a state in Redux DevTools, it doesn't update the Redux store in the app.

In fact, if I do store.getState() in a JS Console connected to the app, I can see that the app's state hasn't changed at all. To be clear, I can change the app's state from Redux DevTools by dispatching new actions, but what I cannot do is make the app go back to a previous state shown in Redux DevTools.

Is this a bug? Or is it a limitation of remote redux-devtools? Is it feasible to make this work or was this a feature that was purposely left unimplemented because it's not feasible or would use too many resources?

Edit: I notice that the docs about remote Redux DevTools use the word "monitor" a lot which maybe implies that its capabilities are read-only? If this is the case, maybe it's a matter of just making this more clear in the docs (which I'd be happy to contribute PRs to). OTOH if it's feasible to make code changes to make this possible, I'd be interested in helping out with that too.

dhruvsakariya commented 1 year ago

@msabramo Have you Found How to make Dispatch action & Time Travel work ?

jonisavo commented 1 year ago

After spending some time investigating, it seems like actions like dispatch and time travel are sent through a channel called sc-[socket id]. However, @redux-devtools/remote seems to be listening to the log channel only. When I listen to both channels, like this...

void (async () => {
  for await (const data of this.socket!.subscribe(channelName)) {
    this.handleMessages(data as Message<S, A>);
  }
})();
void (async () => {
  for await (const data of this.socket!.subscribe('sc-' + this.socket!.id)) {
    this.handleMessages(data as Message<S, A>);
  }
})();

Both dispatching and time travel begin to work as intended. I wonder if this is a bug in the frontend or the remote package? Is the frontend sending these messages through the wrong channel (sc-*) instead of log? Or is the remote package forgetting to listen to the sc-* channel? The sc-* channel seems to be specifically mentioned in the server implementation (cli package), but no documentation on it exists anywhere AFAIK.

Methuselah96 commented 1 year ago

Thanks for investigating this! I wonder if these lines got lost in https://github.com/reduxjs/redux-devtools/pull/1167.

Methuselah96 commented 1 year ago

The new equivalent code responds to the login event, but I don't see where it forwards messages from the sc-* channel to the channelToWatch like it used to.

jonisavo commented 1 year ago

The closest I got was this:

void (async () => {
  for await (const data of agServer.exchange.subscribe('sc-' + socket.id)) {
    void agServer.exchange.transmitPublish(channelToWatch, data);
  }
})();

But since channels are used to transmit information to multiple parties at once, this would send messages to all connected instances. The previously used socket.emit function seems to use events instead of channels, and socket.emit(channelToWatch, data) does not pass type checking. socket.emit(channelToWatch as 'message', data as unknown as any) does not work, either.

If it's OK, I could create a PR that goes for the approach I showed earlier. The downside is that all clients have to listen to sc-* in addition to channelToWatch.