Fork me on GitHub
#re-frame
<
2022-04-08
>
Carl06:04:56

Hi there, I'm experimenting with re-frame mobile app with expo using this template https://github.com/jgoodhcg/create-expo-cljs-app#readme . I've made very minimal changes, having tried to add a button within my screen-main component. The button dispatches the :toggle-test-status event which just toggles a boolean in the local app-db.

[:> paper/Button  {:dark true 
                              :onPress #(>evt [:toggle-test-status])} (str "Toggle test status: " test-status)]
Some of the surrounding code is shown further below. Within screen-main I have subscribed to the entry in the app-db with
test-status (<sub [:test-status])
Inside the let but pressing the button did not result in the button text updating. It wasn't until I subscribed to :test-status within root (which contains screen-main) that I saw DOM change, but subscribing within the top level component results in the whole screen flickering so it's clearly not the right answer, but it does seem to indicate that the event :test-status event is being handled and the subscription also works... I have been suspicious of it being Form 2 issue like described in https://github.com/reagent-project/reagent/blob/master/doc/CreatingReagentComponents.md but I can't seem to spot anything of that nature. Any help for this newbie 😅
(ns app.helpers
  (:require [re-frame.core :refer [subscribe dispatch]]))

(def <sub (comp deref subscribe))

(def >evt dispatch)
(ns app.index
  (: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.fx]
   [app.handlers]
   [app.subscriptions]
   [app.helpers :refer [<sub >evt]]))

(defn list-item [props]
  (r/as-element 
    (let [title (j/get-in props [:item :title])]
      [:> rn/View 
       [:> rn/Text {:style {:background-color :green :color :black}} title]])))

(defn screen-main [props]
  (r/as-element
    (let [version         (<sub [:version])
          list-items (<sub [:list-items])
          theme-selection (<sub [:theme])
          test-status (<sub [:test-status])
          theme           (-> props (j/get :theme))
          expo-version    (-> expo-constants
                              (j/get :default)
                              (j/get :manifest)
                              (j/get :sdkVersion))]

      (print "screen-main reloaded")

      [:> rn/SafeAreaView {:style (tw "flex flex-1")}
       [:> rn/StatusBar {:visibility "hidden"}]
       [:> paper/Surface {:style (tw "flex flex-1 justify-center")}
        [:> rn/View
         [:> paper/Card
          [:> paper/Card.Cover {:source splash-img}]
          [:> paper/Card.Title {:title    "My new expo cljs app!"
                                :subtitle (str "Version: " version)}]
          [:> paper/Card.Content
           [:> paper/Paragraph (str "Using Expo SDK: " expo-version)]
           [:> paper/Paragraph (str "List items here:" list-items)]
           [:> paper/Button  {:dark true 
                              :onPress #(rn/Alert.alert 
                                           "Alert Title" 
                                           "Alert text" 
                                           [{:text "Cancel"}])} (str "Show alert")]

           [:> paper/Button  {:dark true 
                              :onPress #(>evt [:add-list-item {:title "the new item"}])} (str "Add to list")]

           [:> paper/Button  {:dark true 
                              :onPress #(>evt [:toggle-test-status])} (str "Toggle test status: " test-status)]

           [:> rn/FlatList {:data list-items 
                            :renderItem list-item
                            :keyExtractor (fn [x] 
                                            (tap> (j/get x :id))
                                            (j/get x :id))}]

           [:> rn/View {:style (tw "flex flex-row justify-between")}
            [:> paper/Text
             {:style {:color (-> theme
                                 (j/get :colors)
                                 (j/get :accent))}}
             "Dark mode"]
            [:> paper/Switch {:value           (= theme-selection :dark)
                              :on-value-change #(>evt [:set-theme (if (= theme-selection :dark)
                                                                    :light
                                                                    :dark)])}]]]]]]])))


(defn root []
  (let [theme           (<sub [:theme])
        list-items      (<sub [:list-items])
        test-status     (<sub [:test-status])
        !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      "Screen1"
                :component (paper/withTheme screen-main)})]]]))

p-himik07:04:00

For future reference, please attach long code blocks as proper file attachments as opposed to an embedded code block. A wall of code makes it much harder to navigate the message and clutters the channel. As to your issue, I'm 90% certain that it'll be fixed when you replace (paper/withTheme screen-main) with (paper/withTheme (r/reactify-component)) and remove the call to r/as-element from within screen-main.

p-himik07:04:02

The explanation is that by wrapping the body of screen-main in r/as-element, you prevent it from being a part of the reactive context. Any reaction (subscriptions are reactions) used outside of a reactive context cannot trigger re-render.

Carl07:04:36

It really was that simple, thanks @U2FRKM4TW and I will remember to attach code as a file next time 🙂

👍 1