Fork me on GitHub
#cljsrn
<
2022-06-27
>
dsp08:06:04

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?

UlisesMAC10:06:15

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).

UlisesMAC10:06:17

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).

UlisesMAC10:06:58

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

dsp10:06:22

I am indeed using a navigator

dsp10:06:13

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

UlisesMAC11:06:06

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?

dsp11:06:48

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

dsp11:06:07

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

UlisesMAC11:06:11

I'll look at it

dsp11:06:46

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

dsp11:06:48

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.

UlisesMAC11:06:17

Ok I think I know the problem

UlisesMAC11:06:53

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

UlisesMAC11:06:26

And call screens with []

dsp11:06:55

(just to give some context)

UlisesMAC11:06:57

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

dsp11:06:42

but I may not understand how the navigator works

UlisesMAC11:06:45

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

UlisesMAC11:06:34

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

UlisesMAC11:06:04

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

UlisesMAC11:06:21

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

dsp11:06:22

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)

UlisesMAC11:06:47

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

dsp11:06:25

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

dsp11:06:38

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

UlisesMAC11:06:56

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

UlisesMAC11:06:57

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

UlisesMAC11:06:30

There are two ways to solve this

UlisesMAC11:06:00

One is use r/reactify-component

UlisesMAC11:06:32

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

dsp11:06:10

thank you

UlisesMAC11:06:03

[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)

dsp11:06:03

so, no :compoonent here right?

UlisesMAC11:06:03

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

UlisesMAC11:06:29

And screen can be any regular reagent component you wish

UlisesMAC11:06:47

Ignore the js->clj if you dont need it

UlisesMAC11:06:11

Yep, there's no component key

UlisesMAC11:06:42

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

UlisesMAC11:06:11

So the alternative is to pass the view as a child

dsp11:06:16

I don't get any warnings with my current code

dsp11:06:53

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

UlisesMAC11:06:29

This is the warning

UlisesMAC11:06:01

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

dsp11:06:35

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
dsp11:06:06

also in your example,

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

UlisesMAC11:06:03

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

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

dsp11:06:48

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

UlisesMAC11:06:53

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

UlisesMAC11:06:18

And you are using r/as-element

UlisesMAC11:06:38

Ok, give me 10min

UlisesMAC11:06:48

I'll fix your code and send it to you

UlisesMAC11:06:55

I'll try to also explain

dsp11:06:57

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).

UlisesMAC11:06:25

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

UlisesMAC11:06:41

But IMO that wrappers were not very useful

UlisesMAC11:06:36

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

UlisesMAC11:06:46

You could add them later 🙂

UlisesMAC12:06:30

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]]]]))

UlisesMAC12:06:55

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

UlisesMAC12:06:21

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

UlisesMAC12:06:36

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

UlisesMAC12:06:39

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}]]]]))

UlisesMAC12:06:30

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

dsp12:06:45

i get a blank screen in the app

dsp12:06:12

should it not have been verbatim?

dsp12:06:39

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

dsp12:06:45

let me add a placeholder to make sure it is rendered

dsp12:06:09

yep, updated, and it is not visible

UlisesMAC12:06:59

are you reloading your app?

UlisesMAC12:06:03

Just in case

dsp12:06:21

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

UlisesMAC12:06:42

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

dsp12:06:39

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

UlisesMAC12:06:16

any errors in your expo console?

UlisesMAC12:06:31

Maybe the error is caused by another piece of code?

dsp12:06:06

no errors at all

dsp12:06:22

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

UlisesMAC12:06:02

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))

UlisesMAC12:06:22

Now I see the error

UlisesMAC12:06:07

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

UlisesMAC12:06:38

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}]]])

UlisesMAC12:06:04

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

dsp12:06:56

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

UlisesMAC12:06:51

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

dsp12:06:15

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

dsp12:06:37

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

UlisesMAC12:06:21

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

UlisesMAC12:06:29

And share it with you

UlisesMAC12:06:41

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

dsp12:06:06

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

kennytilton12:06:32

@UJA1Z6ZJ7 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.

dsp13:06:01

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 🙂

dima13:06:00

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)})

dsp13:06:13

I'll test it now

dsp13:06:40

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

👍 1
dsp13:06:36

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
loganrios22:07:02

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