PEZ/rn-rf-shadow

Saving file causes the state to be lost

Closed this issue · 13 comments

Hi.

Thank you very much for creating this repository which was a guide to me.

I cloned the repository and followed all the steps (VSCode + Calva).
I am able to run the app and clicking the button successfully updates the counter and I am able to change the UI.

The problem is that saving file (in order for the UI changes to be shown such as changing text color etc.) causes the state
to be lost. When file is saved after doing some changes such as changing the text color, causes the app to be reloaded and eventually the re-frame DB gets reinitialized causing all of the changes to be lost.

So far I have not been able to figure out the reason as I am new to ReactNative and ClojureScript.

Thank you for your help.

PEZ commented

Hi! It was a while since I used this tech stack, but as I recall it, the React Native Fast Refresh thing had this effect. There's a section here about it: https://github.com/PEZ/rn-rf-shadow?tab=readme-ov-file#disabling-expo-fast-refresh

When I use the typescript and useState from ReactNative and after changing the UI and saving the file the state is preserved . So I am assuming, in this case, the re-frame does not keep the state or the DB is initialized.

How does Reacts useState work with Expo does that disable the fast refresh automatically?

P.S. I see the same behavior when I create the React Native CLI project and apply the ClojureScript + ReFrame + Reagent + ShadowCLjs in the same way applied in this repo.

PEZ commented

Are you losing the state even with fast refresh disabled?

I tried to disable the fast refresh by adding localhost:19006/* as Network Request Blocking but I still lost the state.

I checked and saw that there were no blocking requests. So I added the following patterns as well 192.168.10.216:19000/* and localhost:19000/*. But those additions could not also stop the Expo from refreshing the state.

PEZ commented

Do you see the behaviour when developing on the phone with fast refresh disabled? I'm trying to understand if fast refresh is the problem. I know it was very tricky to disable it for the web browser target. And kept changing what worked and not.

If you are asking whether it is on the phone or web then it is on Android target.

PEZ commented

Then you shouldn't need blocking any ports. There should be an option in the developer menu for disabling fast refresh. https://docs.expo.dev/debugging/tools/#developer-menu

Fantastic. Thank you very much. It solved the problem. I am very curious if the same will work with the React Native CLI project.

PEZ commented

Glad you sorted it out!

Yes, you can get there regardless of how you start your project. The parts of any app that are written in ClojureScript will be dynamic and not need the restart-cycle that JavaScript apps typically need. Fast Refresh is basically a restart of the app, and to make state survive things need to be wired up in special ways (I'm guessing it must be some hibernation scheme). While with ClojureScript the app is dynamically built and its parts recompiled and installed without restarts.

That said, with shadow-cljs and its hotreload when you save a file, it is recompiling the whole namespace and namespaces that it depends on (depending on some analysis of what needs to be recompiled). Then shadow-cljs will call the apps entrypoint and things are re-redndered. This recompiling of namespaces can lead to loss of state if, e.g. you have state in an atom that is being redefined when the namespace is re-evaluated. What we do to tell the compiler/evaluator to not redefine a variable when it is reloaded is to use defonce instead of def. This is the source of the defonce macro:

(core/defmacro defonce
  "defs name to have the root value of init iff the named var has no root value,
  else init is unevaluated"
  [x init]
  (core/let [qualified (if (namespace x)
                         x
                         (symbol (core/str (core/-> &env :ns :name)) (name x)))]
    `(when-not (exists? ~qualified)
       (def ~x ~init))))

So it will only be defed if it doesn't exist.

You can check the db.cljs file in this project and you will see that we use defonce for the app database atom (which is what we have set re-frame to use as its state storage).

(Restarting a ClojureScript app will destroy its state, which is why Fast Refresh needs to be disabled.)

Thank you for your reply. I tried to follow exact same steps to create React Native CLI Project but I could not succeed. After disabling fast reload even single color changes started to not appear. I was able to see the code being recompiled by shadow-cljs each time I saved the file but the result did not appear on the screen. I created a demo repository maybe you can have a look and see if I am doing something wrong or not.

https://github.com/namig-tahmazli/cljs-react-native-cli/tree/master

Thank you very much for your assistance.

PEZ commented

I don't seem to have a working ruby environment or something, and fail running the app at all. However, I think you need to tell shadow-cljs which function to call when it has loaded the new compiled code. You could try adding {:dev/after-load true} to the metadata of your app/init function. Something like so:

(defn ^:dev/after-load init []
  (render-root (r/as-element [root])))

I do not know how to thank you. Thank you a lot. Indeed adding ^:dev/after-load causes the shadow-cljs to load properly

PEZ commented

Awesome! Happy I could help!