Fork me on GitHub
#cljsrn
<
2017-05-10
>
kurt-o-sys06:05:52

Hey all. I'm trying to get navigation running - I'm pretty new to RN, let alone to cljsrn - and I just can't make it work. I'm using rum/re-natal and I'm trying out (recommended?) https://reactnavigation.org/ . This is how far I got:

kurt-o-sys06:05:49

(ns my.ns.android.core
  (:require-macros [rum.core :refer [defc]])
  (:require [re-natal.support :as support]
            [rum.core :as rum]))

(set! js/window.React (js/require "react"))
(def ReactNative (js/require "react-native"))

(def ReactNavigation (js/require "react-navigation"))
(def stack-nav (.-StackNavigator ReactNavigation))

(def app-registry (.-AppRegistry ReactNative))
(def text (partial create-element (.-Text ReactNative)))

(defc HelloScreen
  []
  (text "Hello, navigation"))

(defc AnotherScreen
  []
  (text "Hello, another"))

(def App (stack-nav. (clj->js {:Home {:screen (:rum/class (meta HelloScreen))}})))

(defonce root-component-factory (support/make-root-component-factory))

(defn mount-app []
  (support/mount App)
  (init-user-location)
  (state/retreive-markers))

(defn init []
  (mount-app)
  (.registerComponent app-registry "fleeman" (fn [] root-component-factory)))

kurt-o-sys06:05:19

However, this returns an error (see screenshot below)

kurt-o-sys06:05:52

So it seems App is not a react element.

seantempesta06:05:13

I don’t know rum, but it looks like (:rum/class is returning a class?

kurt-o-sys06:05:21

ok, so here are a few new things to try: 1. Make an element from the class: (support/mount (create-element App))

kurt-o-sys06:05:41

Better, but doesn't show the component.

kurt-o-sys06:05:22

2. Replace (:rum/class (meta HelloScreen)) with HelloScreen (a React Native component):

kurt-o-sys06:05:33

React Native tells me it's not a react component... (that's why I had to do the weird thing with (:rum/class (meta ...)))

seantempesta06:05:15

Yeah, React Navigation needs react components to render.

kurt-o-sys06:05:47

Well, right, but HelloScreen is a react component (or at least, it should be, according to the rum docs)

seantempesta06:05:53

So, this part (.registerComponent app-registry "fleeman" (fn [] root-component-factory))) needs to return a react component. What’s the root-component-factory doing?

kurt-o-sys06:05:32

Yeah, that's the helper for rum, apparently:

(ns re-natal.support
  "Helpers and adapters to be able to mount/remount Rum components in a React Native application.")

(def React (js/require "react"))
(def create-class (.-createClass React))
(def create-factory (.-createFactory React))

(defonce root-component (atom nil))
(defonce mounted-element (atom nil))

(defn make-root-component-factory
  "Returns a React Native component factory fn for the root componenet singleton"
  []
  (create-factory
   (create-class
    #js {:getInitialState
         (fn []
           (this-as this
             (if-not @root-component
               (reset! root-component this)
               (throw (js/Error. "ASSERTION FAILED: re-natal.support root component mounted more than once.")))))
         :render
         (fn [] @mounted-element)})))

(defn mount
  "A modified version of rum.core/mount to work with React Native and re-natal.

  Since React Native's root component is a singleton, mount doesn't apply in the
  context of a DOM element (like in React), but applies globally to the app.
  This function mounts/replaces the current "
  [element]
  (reset! mounted-element element)
  (when @root-component
    (.forceUpdate @root-component)))

kurt-o-sys06:05:54

it basically mounts the root component.

kurt-o-sys06:05:32

I'm really new to RN, and thought navigation would be straight forward 😛. It seems there are some libs around, and it's not that easy 😛.

kurt-o-sys06:05:10

(def App (stack-nav. (clj->js {:Home {:screen (create-element HelloScreen)}})))

kurt-o-sys06:05:16

That's the StackNavigator

kurt-o-sys06:05:25

(def ReactNavigation (js/require "react-navigation"))
(def stack-nav (.-StackNavigator ReactNavigation))

seantempesta06:05:41

Nah. Navigation with React Native has never been straightforward. Even on vanilla RN.

kurt-o-sys06:05:22

Yeah... pretty weird, since I don't need complex navigation. It's just 2 (max 3) 'screens'

kurt-o-sys06:05:45

swiping from one to another (or going to another one by pushing a button).

kurt-o-sys06:05:43

I've been thinking of just doing this manually with the RN animations API, but it all looks a bit weird to me. Anyway, not sure how to make it work.

seantempesta06:05:49

Well, I think you need a react component for the :screen prop. Not sure if an element will work.

kurt-o-sys06:05:26

ok. let's see how far I can get (`HelloScreen` is actually a react component, or it should be)

seantempesta06:05:07

Yeah. I started with baby steps. Made everything normal react components and just tried to get basic stuff displaying at first.

kurt-o-sys06:05:48

well, yes, the basics are ok... I was at the stage to put it all together and and navigation 🙂

kurt-o-sys07:05:30

oh, ok... got something working.

kurt-o-sys07:05:38

(defc HelloScreen 
  []
  (view {:style {:alignItems "stretch"
                 :flex       1}}
        (text {:style {:fontSize     30
                       :fontWeight   "100"
                       :marginBottom 20
                       :textAlign    "center"}}
              "Hello")))

(defc AnotherScreen
  []
  (text "Hello, another"))

(def App (stack-nav. (clj->js {:Home {:screen (:rum/class (meta HelloScreen))}})))

(defonce root-component-factory (support/make-root-component-factory))

(defn mount-app []
  (support/mount (create-element App))
  (init-user-location)
  (state/retreive-markers))

kurt-o-sys07:05:48

Now, next step: how to add the titles...

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Welcome',
  };
  render() {
    return <Text>Hello, Navigation!</Text>;
  }
}
Or, how to add static navigationOptions? (I suppose I don't have to worry about the warning - as far as I know, it's something about RN 0.44?)

seantempesta07:05:13

Yeah. I think they are changing the back behavior on Android

seantempesta07:05:28

Here’s what I’m using for navigationOptions:

(defn screen
  "If navigationOptions are specified append to the react-component"
  [react-component navigationOptions]

  (when (and navigationOptions (not= navigationOptions :cljs.spec/invalid))
    (aset react-component "navigationOptions" (clj->js navigationOptions)))

  react-component)

kurt-o-sys07:05:09

right... aset is the thing I was looking for, I guess.

seantempesta07:05:31

It feels kinda hacky, but since they insist on using “ES6 Classes” which are just hacks you gotta do it.

seantempesta07:05:46

Ugh. I wish my navigation library was in better shape. I’m in the process of creating a general purpose cljs wrapper for React Navigation using spec. Then specific libraries can extend the base layer.

kurt-o-sys07:05:41

I'd love a 'general purpose cljs wrapper' 🙂

kurt-o-sys07:05:51

(defc HelloScreen < rum/reactive
  []
  (view {:style {:alignItems "stretch"
                 :flex       1}}
        (text {:style {:fontSize     30
                       :fontWeight   "100"
                       :marginBottom 20
                       :textAlign    "center"}}
              "Hello, navigation")))
(def HelloScreenClass (:rum/class (meta HelloScreen)))
(aset HelloScreenClass "navigationOptions" (clj->js {:title "Welcome!"}))

kurt-o-sys07:05:18

looks hacky in many ways, but well, it works 😛

seantempesta07:05:31

Basically, the base is just a bunch of specs that explain what react navigation is expecting and has simple conformers for converting react components to react elements when needed.

kurt-o-sys07:05:04

really nice.

seantempesta07:05:06

The reagent version inherits the base and extends the conforming specs to also allow reagent components and just transparently converts them to normal react components or elements

kurt-o-sys07:05:20

So it'll be a matter of translating the reagent-specific functions to other wrappers. cool.

seantempesta07:05:23

yeah. Basically the functions say what they should be returning. Either a react component or an element or a function that will return a component or element. With the latter you can do all the (clj->js conversions for props so you can just keep everything in clojurescript.

seantempesta07:05:09

I was hoping to save everyone from re-inventing the wheel and then we could have rum and om wrappers as well.

kurt-o-sys07:05:58

navigation should be pretty straight forward...

seantempesta07:05:27

ha. should is the key word there. but it is getting a lot better. It used to be even worse if you can believe it.

kurt-o-sys07:05:21

well, yes, I believe it (although I actually can't believe it - that's weird)

seantempesta07:05:03

Anyway, half the battle for me was just defining all of the specs. Once it was clear what every function actually wanted (because the docs are kinda confusing) I made faster progress. Hopefully those will help you.

kurt-o-sys07:05:34

It will... thanks a lot!

Dos10:05:15

anyone using expo+cljs in production?

seantempesta15:05:07

@dos: in production? Not yet. But I plan to.