Search code examples
react-nativecidershadow-cljs

Often seeing "REPL command timed out" in Cider with shadow-cljs and React Native. Must restart emulator to fix


I'm working with a basic ReactNative app using Emacs with Cider and ShadowCLJS. I can develop with the REPL pretty consistently but as soon as I accidentally save a file that has a syntax error in it then I lose communication to the REPL. Anything I type results in a delay followed by "REPL command timed out". The only way I have found to fix it is to restart the emulator with npx react-native run-android. But then I lose all the state that I had in the REPL.


Solution

  • This could be a number of different things.

    It might related to the live-reloading that Metro (or Expo) provides. Press Ctrl-M (Cmd-M on Mac) in the emulator to bring up the options to turn off Fast Refresh.

    https://facebook.github.io/react-native/docs/fast-refresh

    https://github.com/thheller/shadow-cljs/issues/469

    If you're still getting this error even after disabling Fast Refresh, it might be because ReactNative doesn't cleanly disconnect old websockets when reloading. Here's a comment from the creator of shadow-cljs.

    its a bug in react-native in that it doesn't disconnect websockets when reloading the app so shadow-cljs thinks the "old" app is still running and tries talking to it (but it never replies) I opened an issue on the RN repo but it was closed due do inactivity for a year or so. nobody cared I guess.

    I found a work-around using ReactNative's AppState and the reference to the websocket from the shadow-cljs dev namespace.

    https://facebook.github.io/react-native/docs/appstate.html

    https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/cljs/devtools/client/react_native.cljs

    (defn on-app-state-change
      "Fixes issue with shadow-cljs repl losing connection to websocket.
    
      Put this in some root component's `component-did-mount`.
      https://stackoverflow.com/questions/40561073/websocket-not-closed-on-reload-appreact-native
      "
      [state]
      (cond
        (= state "background")
        (.close @shadow-rn/socket-ref)
    
        (and (= state "active")
             (nil? @shadow-rn/socket-ref))
        (shadow-rn/ws-connect)))
    
    (defn make-reloader
      [component]
      (let [component-ref (r/atom component)]
        (letfn [(render []
                  (let [component @component-ref]
                    (if (fn? component)
                      (component)
                      component)))]
          (let [wrapper (r/create-class
                         {:render render
                          :component-did-mount
                          (fn []
                            (.addEventListener rn/AppState "change" on-app-state-change))
    
                          :component-will-unmount
                          (fn []
                            (.removeEventListener rn/AppState "change" on-app-state-change))})]
    
            (rn/AppRegistry.registerComponent "Ezmonic" (fn [] wrapper))
            (fn [comp]
              (reset! component-ref comp))))))