Fork me on GitHub
#cljsrn
<
2022-09-29
>
dsp09:09:28

Hi folks. I'm having trouble understanding how I should be using react native elements like https://callstack.github.io/react-native-paper/bottom-navigation.html in clojurescript. Does anyone have an example on how to use elements that use useState? For simple elements like buttons, I can do [:> paper/Button "Button Label"] with no issues. But for more complex elements, I'm a bit lost. Thanks in advance!

lepistane09:09:57

If you shared your code it might be more helpful to give you more precise answer and possible point to issues but this should be helpful to you https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md#hooks

dsp09:09:25

I tried to mentally parse this page, but I think it requires at least some deeper knowledge of plain react and javascript than I already have. Maybe it was a mistake to try and use this stuff without having first learnt to do it in js, but if I had gone that route, I would never have probably bothered with cljs in the first place 🙂 "hooks", "component classes", etc., all new to me. I have an example screen:

(defn open-project-screen [props]
  (r/as-element
   [:> rn/SafeAreaView {:style (tw "flex flex-1")}
    [:> rn/StatusBar {:visibility "hidden"}]
    [:> rn/View {:style (tw "flex flex-1 justify-center px-20")}
     [:> rn/View {:style (tw "py-2")}]
     [:> rn/View {:style (tw "flex-row justify-center py-1")}
      [:> rn/View
       [:> rn/View {:style (tw "py-1")}]
       [:> paper/Text {:style (tw "px-1")} (str "Project: " (<sub [:opened-project]))]
       [:> rn/View {:style (tw "py-1")}]]]
     [:> rn/View {:style (tw "flex-row justify-center py-1")}
      [:> paper/Button {:mode "text" :on-press #(>evt [:navigate "property-details"])} "Open Project"]]]]))
and I want to add https://snack.expo.dev/?name=BottomNavigation&amp;description=https%3A%2F%2Fcallstack.github.io%2Freact-native-paper%2Fbottom-navigation.html&amp;code=import%20*%20as%20React%20from%20%27react%27%3B%0Aimport%20%7B%20BottomNavigation%2C%20Text%20%7D%20from%20%27react-native-paper%27%3B%0A%0Aconst%20MusicRoute%20%3D%20()%20%3D%3E%20%3CText%3EMusic%3C%2FText%3E%3B%0A%0Aconst%20AlbumsRoute%20%3D%20()%20%3D%3E%20%3CText%3EAlbums%3C%2FText%3E%3B%0A%0Aconst%20RecentsRoute%20%3D%20()%20%3D%3E%20%3CText%3ERecents%3C%2FText%3E%3B%0A%0Aconst%20NotificationsRoute%20%3D%20()%20%3D%3E%20%3CText%3ENotifications%3C%2FText%3E%3B%0A%0Aconst%20MyComponent%20%3D%20()%20%3D%3E%20%7B%0A%20%20const%20%5Bindex%2C%20setIndex%5D%20%3D%20React.useState(0)%3B%0A%20%20const%20%5Broutes%5D%20%3D%20React.useState(%5B%0A%20%20%20%20%7B%20key%3A%20%27music%27%2C%20title%3A%20%27Favorites%27%2C%20focusedIcon%3A%20%27heart%27%2C%20unfocusedIcon%3A%20%27heart-outline%27%7D%2C%0A%20%20%20%20%7B%20key%3A%20%27albums%27%2C%20title%3A%20%27Albums%27%2C%20focusedIcon%3A%20%27album%27%20%7D%2C%0A%20%20%20%20%7B%20key%3A%20%27recents%27%2C%20title%3A%20%27Recents%27%2C%20focusedIcon%3A%20%27history%27%20%7D%2C%0A%20%20%20%20%7B%20key%3A%20%27notifications%27%2C%20title%3A%20%27Notifications%27%2C%20focusedIcon%3A%20%27bell%27%2C%20unfocusedIcon%3A%20%27bell-outline%27%20%7D%2C%0A%20%20%5D)%3B%0A%0A%20%20const%20renderScene%20%3D%20BottomNavigation.SceneMap(%7B%0A%20%20%20%20music%3A%20MusicRoute%2C%0A%20%20%20%20albums%3A%20AlbumsRoute%2C%0A%20%20%20%20recents%3A%20RecentsRoute%2C%0A%20%20%20%20notifications%3A%20NotificationsRoute%2C%0A%20%20%7D)%3B%0A%0A%20%20return%20(%0A%20%20%20%20%3CBottomNavigation%0A%20%20%20%20%20%20navigationState%3D%7B%7B%20index%2C%20routes%20%7D%7D%0A%20%20%20%20%20%20onIndexChange%3D%7BsetIndex%7D%0A%20%20%20%20%20%20renderScene%3D%7BrenderScene%7D%0A%20%20%20%20%2F%3E%0A%20%20)%3B%0A%7D%3B%0A%0Aexport%20default%20MyComponent%3B&amp;dependencies=react-native-paper%405.0.0-rc.5,@expo/vector-icons%40%5E13.0.0.

dsp09:09:56

If it requires deeper js interop & react knowledge, I'll use another less complex workaround, just thought maybe this would have been a common use case and maybe there's already live examples somewhere. Google search didn't turn up much though, seems not a lot of published projects using this stack.

dsp09:09:22

(All I'm really looking for is some nice way to have buttons at the bottom of my screen, that when pressed can fire off a re-frame event to navigate. Maybe using such off-the-shelf components are overkill and unnecessary.)

dsp09:09:53

Probably a common pattern for navigation in react native, but I come from a pure backend dev background and this is all still outside of my comfort zone. Maybe I'm "holding it wrong".

lepistane10:09:14

Don't be hard on yourself. React native is quite hard to get i often found myself forgetting a lot of stuff that's why i write notes all the time for versions, install steps etc... Maybe i should've dockerized this 😄 I can't find a simple example of hooks to give to you. I dont think you need to learn JS and RN it may just mess with you head 😄 The only thing that i remember i made recently that had references that could be helpful to you is: https://www.reddit.com/r/Clojure/comments/uvw0mw/how_to_get_autoanimate_working_with_reframe/

dsp10:09:53

Thanks a lot! I'll take a look 🙂

dsp12:09:16

Something else I'm a little unsure of:

<BottomNavigation
      navigationState={{ index, routes }}
      onIndexChange={setIndex}
      renderScene={renderScene}
    />
vs
<Button icon="camera" mode="contained" onPress={() => console.log('Pressed')}>
    Press me
  </Button>
I can mentally translate the bottom into the cljs format pretty easily, but I struggle with understanding the meaning of the literals {{ }}. Straight {} is interpolation, right? i.e. onIndexChange={setIndex}, if I had a variable setIndex, would be {:onIndexChange setIndex} (I think?) However, I'm not sure what onIndexChange={{setIndex}} would be eqivalent of, since i'd map it to {setIndex}, which is a key in a map without a value. So I think I'm misunderstanding what's happening.

dsp12:09:16

Actually I think my understanding is completely incorrect. My primary navigator looks like this:

(ns app.navigator
  (:require
   ["@react-navigation/native" :as nav]
   ["@react-navigation/native-stack" :as n-stack]
   ["react-native-paper" :as paper]
   ["react-native-gesture-handler" :as g]

   [applied-science.js-interop :as j]
   [reagent.core :as r]

   [app.screens.login :refer [login-screen]]
   [app.screens.project :refer [open-project-screen]]
   [app.screens.register :refer [register-screen]]
   [app.screens.open :refer [open-screen]]
   [app.screens.create :refer [create-screen]]
   [app.screens.main :refer [main-screen]]
   [app.screens.property :refer [property-details-screen]]
   [app.screens.welcome :refer [welcome-screen]]
   [app.screens.forgot :refer [forgot-password-screen]]

   [app.fx :refer [!navigation-ref]]
   [app.handlers]
   [app.subscriptions]
   [app.util :refer [<sub >evt]]))

(defn wrap-screen
  [the-screen]
  (g/gestureHandlerRootHOC
   (paper/withTheme
    (r/reactify-component the-screen))))

(def stack (n-stack/createNativeStackNavigator))

(defn stack-navigator [] (-> stack (j/get :Navigator)))

(defn stack-screen [props] [:> (-> stack (j/get :Screen)) props])


(defn root []
  (let [theme           (<sub [:theme])
        !route-name-ref (clojure.core/atom {})]

    [:> paper/Provider
     {:theme (case theme
               :light paper/MD3LightTheme
               :dark  paper/MD3DarkTheme
               paper/MD3LightTheme)}

     [:> 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))})
                          (>evt [:reset-nav-state]))
       :on-state-change (fn [state]
                          (>evt [:save-nav-state state])

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

      [:> (stack-navigator) {:screenOptions {:headerShown false}}
       (stack-screen {:name      "login"
                      :component (wrap-screen login-screen)
                      :options   {}})
       (stack-screen {:name      "main"
                      :component (wrap-screen main-screen)
                      :options   {}})
       (stack-screen {:name      "property-details"
                      :component (wrap-screen property-details-screen)
                      :options   {}})
       (stack-screen {:name      "register"
                      :component (wrap-screen register-screen)
                      :options   {}})
       (stack-screen {:name      "forgot-password"
                      :component (wrap-screen forgot-password-screen)
                      :options   {}})
       (stack-screen {:name      "open-project"
                      :component (wrap-screen open-project-screen)
                      :options {}})
       (stack-screen {:name      "welcome"
                      :component (wrap-screen welcome-screen)
                      :options   {}})
       (stack-screen {:name      "open"
                      :component (wrap-screen open-screen)
                      :options   {}})
       (stack-screen {:name      "create"
                      :component (wrap-screen create-screen)
                      :options   {}})]]]))
ref and others are shown here: https://reactnavigation.org/docs/navigation-container/ the example <NavigationContainer ref={ref}> in my cljs code is a lambda, and it works, so my mental model is completely off about what this actually means. 😞 I think I'll need to set aside a weekend and some proper study time to try and wrap my head around this.

anssikin12:09:18

> However, I'm not sure what onIndexChange={{setIndex}} would be eqivalent of, since i'd map it to {setIndex}, which is a key in a map without a value. So I think I'm misunderstanding what's happening. just quickly eavesdropping, in (react) JS world the double-curlys mean an object shorthand, as in {"setIndex": setIndex} (or in cljs {:setIndex setIndex}). so this would become {:onIndexChange {:setIndex setIndex}}

anssikin12:09:55

(where setIndex is some variable defined before)

dsp13:09:27

navigationState={{ index, routes }}
would then become {:navigationState {:index index :routes routes}} ?

dsp13:09:28

that's what I thought. thanks. still wondering why they are fns in my code, will need to figure out what refs are and whether objects can be replaced with functions that return something rather than literal values

dsp13:09:21

Will come back and dig deeper on the weekend. Cheers for the help.

anssikin13:09:45

👍 refs, as i remember using them with react in browser were properties in react elements that expected a callback, which react passed the reference to the DOM element. so you could store the actual element from that callback. i don't know if this is different in react native though, or your use case.

dima13:09:07

> still wondering why they are fns in my code, will need to figure out what refs are and whether objects can be replaced with functions that return something rather than literal values Same as in React, you can create ref using createRef or set ref using a function. Read more here https://reactjs.org/docs/refs-and-the-dom.html#callback-refs