reduxjs / redux-devtools

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

[Documentation request] expo 49 (hermes) support #1533

Open mrcoles opened 10 months ago

mrcoles commented 10 months ago

tl;dr - Is there a guide or documentation for running redux-devtools with Expo 49?

Hi, thank you for your excellent work on this tool! I’m struggling to get the redux-devtools working with Expo 49+, which is now on the “hermes” JS engine and no longer supports the React Native Debugger (which had redux-devtools baked into it). I’m asking here, because there are lots of unresolved SO posts and forum threads of people running into this issue.

The new debugger opens in a Chrome instance and separately includes the base react devtools too (from the CLI in the terminal shift+m and “Start React devtools”). Ideally the Expo team would add redux-devtools in too, but in the meantime, I’m looking to just run it separately.

The redux-devtools/cli README shows examples of running it on port 8000 (why 8000 is that just arbitrary, what if I have a dev server already using 8000?) and then also mentions something about “Inject to React Native local server”, which doesn’t have enough info to someone who is unfamiliar with the project to understand it and also seems more about vanilla React Native than Expo.

raul-potor commented 9 months ago

I'm having the same problem. Did you manage to get it to work with Expo SDK 49?

kavinsan commented 8 months ago

nothing

liquidvisual commented 7 months ago

Yikes, there's truly no Redux debugging options out there right now (or information) -- why? 😩. I'm definitely going to hold off upgrading Expo - losing Redux debugging would be nightmarish.

raul-potor commented 7 months ago

I was able to make the devtools work on expo 49 using "@redux-devtools/remote": "^0.8.2" and @redux-devtools/cli - install it using npm i -g @redux-devtools/cli. In order to get it to work, you will also need patch-package.

I used this patch (inside patches/@redux-devtools+remote+0.8.2.patch): @redux-devtools+remote+0.8.2.patch

Also don't forget to add the postinstall to your scripts

"postinstall": "patch-package"

For Android, you'll also need to run reverse tcp on ports 8000 and 8081:

adb reverse tcp:8000 tcp:8000
adb reverse tcp:8081 tcp:8081

Then open the redux devtools using this command

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

This is what worked for me, hope it helps

liquidvisual commented 7 months ago

Legend! Thanks so much for the info - will give it a go later today. 😃

YosefBlandin commented 7 months ago

The solution proposed by @raul-potor didn't work for me, when I opened the Redux DevTools I saw the following error in the console: WebSocket connection to 'ws://localhost:8081/socketcluster/' failed: Connection closed before receiving a handshake response. I was able to see that error because in the settings tab I switched from no remote connection to use local (custom) server, and I entered my hostname and port.

raul-potor commented 7 months ago

@YosefBlandin also check your store configuration, mine looks like this:

import { devToolsEnhancer } from "@redux-devtools/remote";
const store = configureStore({
  reducer: {
    ...
  },
  devTools: process.env.NODE_ENV === "development",
  enhancers:
    (process.env.NODE_ENV === "development" && [
      devToolsEnhancer({ hostname: "localhost", port: 8000 }),
    ]) ||
    undefined,
});

You are right, I forgot to mention that you need to change the connection settings in the devtools to use local (custom) server and set the hostname to localhost and port to 8000

Also, I recall needing to install react-native-get-random-values as well otherwise it would not connect

ddavydov commented 7 months ago

doesn't work for me also, Expo SDK 50. I don't see any actions being reported

johnhamlin commented 7 months ago

It doesn't have time traveling, but if you just need to inspect your store and see a timeline of your actions, you can use Reactotron until we get proper Redux Devtools working with Hermes.

trydalch commented 7 months ago

Appreciate the update, @johnhamlin . Any idea on timeline to get it working with Hermes?

johnhamlin commented 7 months ago

Reactotron works with hermes, @trydalch. I'm not a contributor to this repo, so I don't have any inside info. I'm just using Reactotron to bridge the gap while I wait.

YosefBlandin commented 6 months ago

Great @johnhamlin, if you have any resources we can use in order to implement Reactotron, it'll be helpful for us. Thank you.

AldoMX commented 6 months ago

These are my notes, they assume TypeScript, Expo 50, Expo Router v3, Redux Toolkit v2:

  1. Install Reactotron

    brew install --cask reactotron
  2. Add to project

    npx expo install expo-constants
    npm install --save-dev reactotron-react-native reactotron-redux

src/util/reactotron.ts

import Constants from 'expo-constants';
import Reactotron from 'reactotron-react-native';
import { reactotronRedux } from 'reactotron-redux';

const tron = Reactotron.configure({
  getClientId: async () => Constants.installationId,
  name: Constants.expoConfig?.name,
})
  .useReactNative()
  .use(reactotronRedux())
  .connect();

export default tron;

src/store.ts

configureStore({
  // ...
  enhancers: (getDefaultEnhancers) => {
    if (__DEV__) {
      const { default: Reactotron } = require('@/util/reactotron');
      return getDefaultEnhancers().concat(Reactotron.createEnhancer());
    }
    return getDefaultEnhancers();
  },
});

src/app/_layout.tsx

// ...

if (__DEV__) {
  require('@/util/reactotron');
}

export default function RootLayout() {
  // ...
}

OPTIONAL: Add console.tron global.

src/global.d.ts

import Reactotron from '@/util/reactotron';

declare global {
  interface Console {
    tron?: typeof Reactotron;
  }
}

src/app/_layout.tsx

// ...

if (__DEV__) {
  const { default: tron } = require('@/util/reactotron');
  console.tron = tron;
}

export default function RootLayout() {
  // ...
}
YosefBlandin commented 6 months ago

Excellent, I followed all the steps that @AldoMX mentioned and now I can see all the requests, actions and additionally I have the possibility to subscribe to state changes. We can repeat actions as well using Reactotron. It's a very good alternative in order to develop without having to guess or add console logs across all the reducers. Thank you. 👏👏👏👏👏

kavinsan commented 6 months ago

Excellent, I followed all the steps that @AldoMX mentioned and now I can see all the requests, actions and additionally I have the possibility to subscribe to state changes. We can repeat actions as well using Reactotron. It's a very good alternative in order to develop without having to guess or add console logs across all the reducers. Thank you. 👏👏👏👏👏

Thanks for verifying the solution @AldoMX suggested, I will also try sometime and report back on any success/failures

pmk1c commented 5 months ago

There seem to be two issues for Expo / RN support left to be fixed. This one is still not completely fixed https://github.com/reduxjs/redux-devtools/issues/1382#issuecomment-2025688714 and when it is, the App still doesn't get messages from the Devtools server, for me.

What is working for me is adding a Symbol-Polyfill to my App-Entrypoint:

Symbol.asyncIterator ??= Symbol.for("Symbol.asyncIterator");

And using the following patch for @redux-devtools/remote, which hacks the implementation by not waiting for any message from the Devtools server. Since it still does not receive any messages from the Devtools server, it will be read only (so time-travelling will not work), but it's better than nothing right now:

diff --git a/lib/cjs/devTools.js b/lib/cjs/devTools.js
index a6eeb9bee71d032f3264b08babc3061cebe04cf6..f746a70b4771ff59ac5de3c0bd760a43507bc58b 100644
--- a/lib/cjs/devTools.js
+++ b/lib/cjs/devTools.js
@@ -189,6 +189,7 @@ class DevToolsEnhancer {
       }
     })();
     this.started = true;
+    this.isMonitored = true;
     this.relay('START');
   }
   stop = keepConnected => {
AldoMX commented 5 months ago

Reactotron is crashing the application when you build it for iOS/Release, see this issue for more details: https://github.com/facebook/hermes/issues/1228

I updated my notes above to prevent this crash from happening, the only changed file is src/store.ts:

configureStore({
  // ...
  enhancers: (getDefaultEnhancers) => {
    if (__DEV__) {
      const { default: Reactotron } = require('@/util/reactotron');
      return getDefaultEnhancers().concat(Reactotron.createEnhancer());
    }
    return getDefaultEnhancers();
  },
});
YosefBlandin commented 5 months ago

I had to remove Reactotron from my project in order to get working the iOS/Release. Basically after uploading the application to Apple, they had been rejecting the application due a crash in different Apple devices, they don't provide so much details, so this was my solution for those crashes they were reporting.

pmk1c commented 4 months ago

I tried figuring out the problems with Hermes, React Native and Remote Devtools a little bit more.

See: https://github.com/reduxjs/redux-devtools/issues/1382#issuecomment-2093584916 on how to fix the asyncIterator-error. Unfortunately it doesn't fix the remote devtools, since no actions get logged.

What I discovered: Connection works, but the remote devtools seem to listen to the wrong channel. It listens to the channel respond, but the devtools cli sends events through a channel called sc-SOCKET_ID. I'm quite sure I'm missing something, but for me it looks, like the implementation of @redux-devtools/cli and @redux-devtools/remote doesn't match. Either there is a bug in @redux-devtools/cli announcing the respond-channel when it should announce the sc-SOCKET_ID-channel, or @redux-devtools/remote should ignore the announced channel and always listen to the sc-SOCKET_ID-channel. 🤔

theogravity commented 3 months ago

I came here via a redux devtools expo search and if you are using expo v50, you should try using this plugin this person developed:

https://github.com/matt-oakes/redux-devtools-expo-dev-plugin

Discovered via https://github.com/expo/dev-plugins/issues/20

It does work for me, although I had to use the react toolkit example dir as I use RTK.

Then in the CLI that runs expo, use shift + m to get the selector to open it

liquidvisual commented 3 months ago

@theogravity Thanks for sharing, I'll be trying it out soon. Were there any pain points in having to use the legacy createStore over configureStore?

theogravity commented 3 months ago

Just one. The documentation says that you have to use createStore instead of configureStore due to an RTK issue.

So I went from:

import { configureStore } from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import auth from '../auth/authReducer';

const store = configureStore({
  reducer: { auth },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// Typed dispatch
export const useTypedDispatch: () => AppDispatch = useDispatch;

// Typed selector
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

export default store;

To:

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import devToolsEnhancer from 'redux-devtools-expo-dev-plugin';
import { combineSlices, createStore } from '@reduxjs/toolkit';
import { authSlice } from '../auth/authReducer';

const reducers = combineSlices(authSlice);

const store = createStore(reducers, devToolsEnhancer());

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// Typed dispatch
export const useTypedDispatch: () => AppDispatch = useDispatch;

// Typed selector
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

export default store;

I had to export the slice directly vs the reducer in the working version.

I also saw some warnings in the CLI around unauthorized requests, but I'm not sure if that affected functionality.

markerikson commented 3 months ago

@theogravity :

that's incorrect, in that any use of createStore can and should be replaced with the equivalent configureStore call instead, in any Redux app. createStore is definitely not required for anything.

update

Hmm. Okay, reading that repo, it says:

To properly install using Redux Toolkit you need to wrap all of the default middleware and enhancers with the composeWithDevTools function. Unfortunately, it's not currently possible to do this while using configureStore, as it does not expose the ability to do this.

I'm still not sure that's correct.

All composeWithDevtools does is add the DevTools enhancer as the last enhancer, after the middleware and anything else.

You ought to be able to get the same result with (untested):

configureStore({
  reducer,
  devTools: false,
  enhancers: (getDefaultEnhancers) => {
    return getDefaultEnhancers().concat(someAdditionalEnhancerHere)
  }
})
theogravity commented 3 months ago

@theogravity :

that's incorrect, in that any use of createStore can and should be replaced with the equivalent configureStore call instead, in any Redux app. createStore is definitely not required for anything.

update

Hmm. Okay, reading that repo, it says:

To properly install using Redux Toolkit you need to wrap all of the default middleware and enhancers with the composeWithDevTools function. Unfortunately, it's not currently possible to do this while using configureStore, as it does not expose the ability to do this.

I'm still not sure that's correct.

All composeWithDevtools does is add the DevTools enhancer as the last enhancer, after the middleware and anything else.

You ought to be able to get the same result with (untested):

configureStore({
  reducer,
  devTools: false,
  enhancers: (getDefaultEnhancers) => {
    return getDefaultEnhancers().concat(someAdditionalEnhancerHere)
  }
})

Thanks. I updated my code accordingly and it does work:

import { configureStore } from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import devToolsEnhancer from 'redux-devtools-expo-dev-plugin';
import auth from '../auth/authReducer';

const store = configureStore({
  reducer: { auth },
  devTools: false,
  // @ts-ignore
  enhancers: (getDefaultEnhancers) => {
    return getDefaultEnhancers().concat(devToolsEnhancer());
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// Typed dispatch
export const useTypedDispatch: () => AppDispatch = useDispatch;

// Typed selector
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

export default store;
markerikson commented 3 months ago

Investigating a bit further:

I see the author previously opened up this discussion thread, where they asked for a way to customize the composition behavior in configureStore (and amusingly, I wrote the exact same example snippet):

in that, they mentioned that the redux-devtools-remote package has a slightly different version of composeWithDevTools() that adds a "preEnhancer" before the other enhancers too. I had looked into it and determined that all the "preEnhancer" does is lock updates to the store if you have that setting applied, which isn't needed to view state updates.

So yeah, the code snippet I gave should work, and so your usage looks correct!

matt-oakes commented 3 months ago

Thanks for looking into that @markerikson. I might still add a way to include that preEnhancer, just so everything works as expected, but it seems like it's possible for most people to get what they need already 👍