cljsrn 2022-06-27

hi folks. scratching my head a little on how I can dynamically update an element. am using expo & shadow-cljs & re-frame. example, I have a screen, which I can navigate to:

(defn app-configuration [props]
  (r/as-element
   [:> rn/SafeAreaView {:style (tw "flex flex-1")}
    [:> rn/StatusBar {:visibility "hidden"}]
    [:> rn/ScrollView
     [:> rn/View
      [:> rn/Text (<sub [:text])]
      [:> rn/TextInput {:style (tw "flex-1")
                        :onChangeText #(>evt [:set-text %])}]]]]))
when I edit TextInput's value, I can check the result is indeed updated correctly at the REPL by executing (<sub [:text]), however the value of the rn/Text label is not dynamically updated to match the subscription, only visible on re-visiting the screen. (i.e., seems (<sub [:text]) is only executed the once, and not dynamically updated. <sub is just (def <sub (comp deref subscribe)) am I missing something?

I guess you're using something like React Navigation. I've had the same behavior a lot of times, it appears to be that the navigators have some kind of cache for the mounted components in order to increase the performance (I guess).

What I do: I usually have a namespace where I define my navigator, so I set the instruction for shadow to reload it always I make a change, also, I persits the navigation state (through a defonce atom at first, then using the device's local storage).

I read again and realized your problem is different. Why are you using (r/as-element ,,,) at the top of your function? If you can share more code it could help

I am indeed using a navigator

I'll throw the code somewhere, appreciate the help.

If you're using this component just as a regular view, I think you shouldn't be using r/as-element. Or are you trying to pass this fn as a parameter to other component?

bear in mind, I still don't really know what I'm doing fully, am very new to this, first real experience with react or react native. you can see here that i define the fn as a component for the screen: https://github.com/dspearson/cljsrn-test/blob/master/src/app/index.cljs#L140

line 85 there I have on-press for a button that navigates

https://github.com/dspearson/cljsrn-test/blob/master/src/app/views.cljs#L28 is the view that i navigate to, which has the dispatch and event subscription that doesn't get updated live in the app, only on re-navigation

if I'm doing something horribly wrong, fair enough, I admit I don't really fully understand how all the bits fit together. have been trying to find working PoC examples to base my stuff off of, or looking at larger applicaations like status-react, but what i have right now is as barebones as i can imagine, i.e. a main screen, a way to navigate, and trying to live update & fetch.

Ok I think I know the problem

I'm not familiar with what j/get does, I usually extract the js object keys using the regular cljs interop. But if that does the same as extracting the key with .- then you should remove the parentheses in navigatior

And call screens with []

(just to give some context)

[:> navigator {:header-mode "none"}
       [:> screen {:name "entrypoint"
                :component (paper/withTheme screen-main)}]
       [:> screen {:name "app-configuration"
                :component (paper/withThem ,,,

but I may not understand how the navigator works

Try with the code (edited) that I sent. (Sorry for not to put it complete, I'm in my phone)

That navigators are a little bit complex. I'm seriously thinking on creating a navigator that works well with hot-reload

Also, you could try with a small example: https://github.com/PEZ/rn-rf-shadow You would need to add the navigators on your own if you clone this repo

Also, maybe starting with ReactNative as a first approach to React is a little hard IMO, because some errors are strange and not easily to identify as when working in a web browser

when removing (),

Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.%s, 
    in app$index$navigator (created by app.index.root)
    in EnsureSingleNavigator
    in BaseNavigationContainer
    in ThemeProvider
    in NavigationContainer (created by app.index.root)
    in ThemeProvider (created by Provider)
    in RCTView (created by View)
    in View (created by Portal.Host)
    in Portal.Host (created by Provider)
    in Provider (created by app.index.root)
    in app.index.root
    in Unknown (created by ExpoRoot)
    in ExpoRoot
    in RCTView (created by View)
    in View (created by AppContainer)
    in RCTView (created by View)
    in View (created by AppContainer)
    in AppContainer
    in main(RootComponent)

I see, I think I know how to fix it. Just a second

and, I mean, it works as is for navigation, just not the state being updated

but yeah if you know what's wrong, I'd be interested

That happens because your component (your view) is not a component, is a function

The :component key in the screen is expecting you to pass an element as a funcion

There are two ways to solve this

One is use r/reactify-component

I'm going yo share you some code of one of my working apps, so you could look at it and replicate

[screen {:name    :screen/add-package
               :options {:title "Nuevo Paquete"
                         :icon  add-package-icon-r}}
       add-package/screen-fn]
      ;;
      [screen {:name    :screen/packages-list
               :options {:title "Tus Paquetes"
                         :icon  packages-list-r}}
       packages-list/screen-fn]
screen here is the same you already have in your code, so don't worry about it. (I'll share more)

so, no :compoonent here right?

(defn screen-fn [props]
  (r/as-element [screen (js->clj props :keywordize-keys true)]))

And screen can be any regular reagent component you wish

Ignore the js->clj if you dont need it

Yep, there's no component key

There's a reason, if you pass a reactified component to the :component key (something that is needed if you use that key), React Navigation will throw a warning about the name of the component, it's very annoying, and we cant fix it since we do not set the resulting name

So the alternative is to pass the view as a child

I don't get any warnings with my current code

the confusion I have is why my stuff "mostly works". but I will see if I can refactor, though I am also trying to understand what you changed

This is the warning

If you understand React, maybe the ReactNavigation docs could help you. You are pretty close to the solution

I just don't understand how it works with https://github.com/armincerf/kalm-mobile/blob/master/src/app/index.cljs where they do it the exact same way, are these examples wrong?

👀 1

also in your example,

(defn screen-fn [props]
  (r/as-element [screen (js->clj props :keywordize-keys true)]))
where is screen here?

screen is whatever you want, it could be your view, e.g.

(defn screen []
  [ ,, ;; the view you want to mount])

since my screens already do r/as-element, can i not just do (screen)

Looking at the example repo you sent, they are using r/reactify-component in the :component key

And you are using r/as-element

I'll fix your code and send it to you

I'll try to also explain

thanks again for the help. it's really quite confusing as a beginner (only cos my stuff works with no errors/warnings, just unwanted behaviour).

I'm now in my Editor, I se why it was needed to call navigator and screen as functions

But IMO that wrappers were not very useful

I'm going to remove the paper/withTheme calls, because I don't know what they're doing

You could add them later 🙂

OK, I don't have a working enviroment, just my editor, but this should work: From stack to root:

(def stack (rn-stack/createStackNavigator))

(def navigator (.-Navigator stack))

(def screen (.-Screen stack))

(defn your-view [props]
  [:> rn/View
   [:> rn/Text (<sub [:text])]
   [:> rn/TextInput {:style        (tw "flex-1")
                     :onChangeText #(>evt [:set-text %])}]])

(defn your-view-fn [props]
  (r/as-element [screen (js->clj props :keywordize-keys true)]))


(defn root []
  (let [theme           (<sub [:theme])
        !route-name-ref (clojure.core/atom {})
        !navigation-ref (clojure.core/atom {})]
    [:> paper/Provider
     {:theme (case theme
               :light paper/DefaultTheme
               :dark paper/DarkTheme
               paper/DarkTheme)}
     [:> nav/NavigationContainer
      {:ref             (fn [el] (reset! !navigation-ref el))
       :on-ready        (fn []
                          (swap! !route-name-ref merge {:current (-> @!navigation-ref
                                                                     (j/call :getCurrentRoute)
                                                                     (j/get :name))}))
       :on-state-change (fn []
                          (let [prev-route-name    (-> @!route-name-ref :current)
                                current-route-name (-> @!navigation-ref
                                                       (j/call :getCurrentRoute)
                                                       (j/get :name))]
                            (when (not= prev-route-name current-route-name)
                              ;; This is where you can do side effecty things like analytics
                              (>evt [:some-fx-example (str "New screen encountered " current-route-name)]))
                            (swap! !route-name-ref merge {:current current-route-name})))}

      [:> navigator {:header-mode "none"}
       [:> screen {:name "entrypoint"}
        your-view-fn]]]]))

Please do copy/paste and if a problem arises tell me

Also I'll add an example using the :component key

(but first I'd like to know if this works)

Here's the code w/ the second example included:

(def stack (rn-stack/createStackNavigator))

(def navigator (.-Navigator stack))

(def screen (.-Screen stack))

(defn your-view [props]
  [:> rn/View
   [:> rn/Text (<sub [:text])]
   [:> rn/TextInput {:style        (tw "flex-1")
                     :onChangeText #(>evt [:set-text %])}]])

(defn your-view-fn [props]
  (r/as-element [screen (js->clj props :keywordize-keys true)]))

;; Second example
(defn your-view-2 [props]
  [:> rn/View
   [:> rn/Text "Another View"]
   [:> rn/Text (<sub [:text])]
   [:> rn/TextInput {:style        (tw "flex-1")
                     :onChangeText #(>evt [:set-text %])}]])

(def your-view-2-reactified (r/reactify-component your-view-2))

(defn root []
  (let [theme           (<sub [:theme])
        !route-name-ref (clojure.core/atom {})
        !navigation-ref (clojure.core/atom {})]
    [:> paper/Provider
     {:theme (case theme
               :light paper/DefaultTheme
               :dark paper/DarkTheme
               paper/DarkTheme)}
     [:> nav/NavigationContainer
      {:ref             (fn [el] (reset! !navigation-ref el))
       :on-ready        (fn []
                          (swap! !route-name-ref merge {:current (-> @!navigation-ref
                                                                     (j/call :getCurrentRoute)
                                                                     (j/get :name))}))
       :on-state-change (fn []
                          (let [prev-route-name    (-> @!route-name-ref :current)
                                current-route-name (-> @!navigation-ref
                                                       (j/call :getCurrentRoute)
                                                       (j/get :name))]
                            (when (not= prev-route-name current-route-name)
                              ;; This is where you can do side effecty things like analytics
                              (>evt [:some-fx-example (str "New screen encountered " current-route-name)]))
                            (swap! !route-name-ref merge {:current current-route-name})))}

      [:> navigator {:header-mode "none"}
       [:> screen {:name "entrypoint"}
        your-view-fn]
       [:> screen {:name      "view-2"
                   :component your-view-2-reactified}]]]]))

@dsp do you had any problems? The second example should rise a warning

i get a blank screen in the app

should it not have been verbatim?

ah wait, no that's expected, two empty text elements

let me add a placeholder to make sure it is rendered

yep, updated, and it is not visible

are you reloading your app?

i will do a full start from scratch, i.e. shadow-cljs watch app and yarn start

Yep, that is good, ok, I'll try another thing, please give me a second

› Reloading apps
Android Bundling complete 129ms
Android Running app on sdk_gphone64_x86_64
shadow-cljs #4 ready!
(and, yeah still blank.)

any errors in your expo console?

Maybe the error is caused by another piece of code?

no errors at all

neither in the app, nor in the console, or shadow-cljs

Ok, please copy/paste this (I commented & deleted a lot of code, if a dependency in the require is wrong, please correct it)

(ns frontend.tmp
  (:require
   ["@react-navigation/native" :as nav]
   ["@react-navigation/stack" :as rn-stack]
   ["expo" :as ex]
   ["expo-constants" :as expo-constants]
   ["react" :as react]
   ["react-native" :as rn]
   ;["react-native-paper" :as paper]
   ;["tailwind-rn" :default tailwind-rn]

   ;[applied-science.js-interop :as j]
   [reagent.core :as r]
   [re-frame.core :refer [dispatch-sync]]
   ;[shadow.expo :as expo]

   ;[app.views :as views]

   ;[app.db]
   ;[app.fx]
   ;[app.handlers]
   ;[app.subscriptions]
   [app.helpers :refer [<sub >evt]]
   )
  )


(def stack (rn-stack/createStackNavigator))
(def navigator (.-Navigator stack))
(def screen (.-Screen stack))

(defn your-view [props]
  [:> rn/View
   [:> rn/Text (<sub [:text])]
   [:> rn/TextInput {:style        (tw "flex-1")
                     :onChangeText #(>evt [:set-text %])}]])

(defn your-view-fn [props]
  (r/as-element [screen (js->clj props :keywordize-keys true)]))

;; Second example
(defn your-view-2 [props]
  [:> rn/View
   [:> rn/Text "Another View"]
   [:> rn/Text (<sub [:text])]
   [:> rn/TextInput {:style        (tw "flex-1")
                     :onChangeText #(>evt [:set-text %])}]])

(def your-view-2-reactified (r/reactify-component your-view-2))

(defn root []
  [:> nav/NavigationContainer {}
   [:> navigator
    [:> screen {:name "entrypoint"}
     your-view-fn]
    [:> screen {:name      "view-2"
                :component your-view-2-reactified}]]])

(defn start
  {:dev/after-load true}
  []
  (expo/render-root (r/as-element [root])))


(defn init []
  (dispatch-sync [:initialize-db])
  (dispatch-sync [:set-version version])
  (start))

Now I see the error

Ignore the last code I sent (i'm going to delete it)

In your original code, from stack to root:

(def stack (rn-stack/createStackNavigator))
(def navigator (.-Navigator stack))
(def screen (.-Screen stack))

(defn your-view [props]
  [:> rn/View
   [:> rn/Text (<sub [:text])]
   [:> rn/TextInput {:style        (tw "flex-1")
                     :onChangeText #(>evt [:set-text %])}]])

(defn your-view-fn [props]
  (r/as-element [your-view (js->clj props :keywordize-keys true)]))

;; Second example
(defn your-view-2 [props]
  [:> rn/View
   [:> rn/Text "Another View"]
   [:> rn/Text (<sub [:text])]
   [:> rn/TextInput {:style        (tw "flex-1")
                     :onChangeText #(>evt [:set-text %])}]])

(def your-view-2-reactified (r/reactify-component your-view-2))

(defn root []
  [:> nav/NavigationContainer {}
   [:> navigator
    [:> screen {:name "entrypoint"}
     your-view-fn]
    [:> screen {:name      "view-2"
                :component your-view-2-reactified}]]])

the problem was in the r/as-element I was calling to [screen ,, instead of [your-view ,,,

now I just have the word 'entrypoint' in my app

I could help you today later, is not a hard problem but I don't know what is happening without being able to run the code

I'll be around later, just ping me if you have the time 🙂 appreciate it!

I'll see if I can play around with it myself too, but also in meetings for the next few hours too, woo

I'm about to start my job right now, so maybe in about 10 hours I'll be free. I could create a simple working example w/ ReactNavigation

And share it with you

IDK what the problem is, but it should be something very simple

that'd be great. all i need to get started is a barebones example that has 2 screens, with navigation between them, and using dispatch events to live-update

@dsp wrote: "it's really quite confusing as a beginner (only cos my stuff works with no errors/warnings, just unwanted behaviour)." <crankyoldfart> Neither does it help a forty plus year veteran full time Lisp/Clojure developer of front ends. But this is what the rugrats have given us with their half-baked approach to software. </crankyoldfart> Seriously, gird your loins and get used to SO and this excellent Slack channel: software devs no longer take the time to write the additional code required to detect user silliness, nor even the negligible added time required to include a little info in the errors they do report. Pro tip: commit often. When your app stops working out of nowhere, copy out any useful work, stash and start over, committing in even tinier bites.

yep. just tried again with the latest version & clean templates from the example repo i was using, https://github.com/dspearson/cljsrn-test/tree/exp2 - same issue, and this one indeed automatically came with 2 screens. so i guess there is a problem with the template, or in the simple usage of subscriptions and events i am doing. c'est la vie, will wait for the simple poc UlisesMAC said he'd do later 🙂

Not sure what paper/withTheme is doing, but it works fine if you remove it, such that screen definition would look like this:

(screen {:name "app-configuration"
                :component (r/reactify-component views/app-configuration)})

I'll test it now

indeed, seems that just the reactify-component call is the thing that was missing to make it all work as expected

👍 1

thanks a lot! I'll do some reading to figure out what exactly this does and why it's needed, but this is plenty for me to start experimenting.

👍 1

Thanks so much for this thread everyone, I've been fighting this exact problem for two days and finding this information was a godsend 🙏