Skip to content
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

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

Open
mrcoles opened this issue Nov 6, 2023 · 26 comments
Open

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

mrcoles opened this issue Nov 6, 2023 · 26 comments

Comments

@mrcoles
Copy link

mrcoles commented Nov 6, 2023

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
Copy link

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

@kavinsan
Copy link

nothing

@liquidvisual
Copy link

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
Copy link

raul-potor commented Jan 19, 2024

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
Copy link

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

@YosefBlandin
Copy link

YosefBlandin commented Jan 24, 2024

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
Copy link

raul-potor commented Jan 24, 2024

@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
Copy link

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

@johnhamlin
Copy link

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
Copy link

trydalch commented Feb 8, 2024

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

@johnhamlin
Copy link

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.

@reduxjs reduxjs deleted a comment from fai8146767 Feb 16, 2024
@YosefBlandin
Copy link

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

@AldoMX
Copy link

AldoMX commented Feb 24, 2024

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

  1. Install Reactotron
brew install --cask reactotron
  1. 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
Copy link

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
Copy link

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
Copy link

pmk1c commented Mar 29, 2024

There seem to be two issues for Expo / RN support left to be fixed. This one is still not completely fixed #1382 (comment) 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
Copy link

AldoMX commented Apr 1, 2024

Reactotron is crashing the application when you build it for iOS/Release, see this issue for more details:
facebook/hermes#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
Copy link

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.

@reduxjs reduxjs deleted a comment from fai8146767 Apr 12, 2024
@pmk1c
Copy link

pmk1c commented May 3, 2024

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

See: #1382 (comment) 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
Copy link

theogravity commented Jun 4, 2024

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 expo/dev-plugins#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
Copy link

@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
Copy link

theogravity commented Jun 4, 2024

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
Copy link

markerikson commented Jun 4, 2024

@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
Copy link

theogravity commented Jun 4, 2024

@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
Copy link

markerikson commented Jun 4, 2024

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
Copy link
Contributor

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 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests