Fork me on GitHub
#cljsrn
<
2017-01-15
>
ejelome01:01:57

How would someone know if it's :rowHasChanged or :row-has-changed? except of course looking at someone else's project everytime you wonder what's the right keyword

levitanong05:01:01

@ejelome unless thereโ€™s some special rule, it should be camelCase. so :rowHasChanged.

ejelome12:01:38

^ but how did you know it's the right rule? XD

ejelome12:01:15

because for instance, there's also :on-change-text but not :onChangeText

ejelome15:01:59

any quick example for ListView? other repo samples are too much to bare

paulspencerwilliams15:01:49

So having spent an hour or so re-acquainting myself with Re-frame albeit on React Native, I'm suprised how familiar it feels. I'd not done any re-frame stuff for maybe a year but I'm immediately jumping into adding subs etc. Thank you to all that helped this project and it's dependencies to where they are now. Really impressive.

pesterhazy15:01:24

@ejelome reagent automatically converts kebab-case to camelCase

pesterhazy15:01:37

@ejelome what's your question about list-views?

paulspencerwilliams15:01:06

Saying that, one thing that I'm not familiar with is how to persist the app-db so it survives application restarts. Is there a pattern for this or documentation that I'm falling to find on this subject?

ejelome15:01:11

ahh, so it doesn't matter if I use kebab-casing?

ejelome15:01:22

since it will just be converted to camelCase

pesterhazy15:01:28

mostly it doesn't matter, correct

pesterhazy15:01:36

I mean for props

pesterhazy15:01:47

for nested maps passed to components, perhaps it does matter

ejelome15:01:11

I see, thanks, yeah, I'm confused with props, some examples uses kebab, others use camel case, and I don't even know how people know the right casing for each props

pesterhazy15:01:11

if you share what you have w/r/t a list-view, I can see if I can spot an issue

pesterhazy15:01:56

it is confusing: HTML has kebab-case, JSX uses camelCase, Reagent goes back to kebab-case ๐Ÿ™‚

ejelome15:01:38

XD correct, that's really confusing until you said it auto converts to camel case anyway, thanks!

ejelome15:01:55

I'll try once more, then if still fails, I'll let you know (about the ListView) ๐Ÿ™‚

paulspencerwilliams15:01:34

All this talk of kebabs is getting me hungry...

pesterhazy15:01:09

@paulspencerwilliams you can persist in LocalStorage

pesterhazy15:01:19

or on the server, if you keep a session

pesterhazy15:01:24

pretty much same as on the web

pesterhazy15:01:15

@ejelome we should start a cljsrn cookbook with examples for people so it's easier to get started

seantempesta15:01:21

ListViews are kind of a pain in the ass if you want to use section headers. Hereโ€™s a helper function I wrote to make generating the DataSource easier.

(defn gen-cloneWithRowsAndSections-args
  "
  INPUT:
  section-v-m = [{:id 2 :name \"Group 1\"} {:id 3 :name \"Group 2\"}]
  row-v-m  = [{:id 5 :firstName \"Althea\"} {:id 6 :firstName \"Alycia\"}]
  section-id->row-ids = {2 #{5} 3 #{6}}
  section-id-key = :id
  row-id-key = :id

  OUTPUT:
  {:section-ids [2 3]
   :row-ids     [[5] [6]]
   :data-blob   {2 {:id 2, :name \"Group 1\"}
                 3 {:id 3, :name \"Group 2\"}
                 5 {:id 5, :firstName \"Althea\"}
                 6 {:id 6, :firstName \"Alycia\"}}}
  "
  [section-v-m row-v-m section-id->row-ids section-id-key row-id-key]
  (trace [section-v-m row-v-m section-id->row-ids section-id-key row-id-key])
  (if (empty? row-v-m)
    {:section-ids []
     :row-ids     []
     :data-blob   {}}
  (let [sorted-data-ids (mapv row-id-key row-v-m)
        section-and-row-ids (reduce (fn [accum svm]
                                      (let [section-id (get svm section-id-key)
                                            filter-pred (get section-id->row-ids section-id #{})
                                            row-ids (filterv filter-pred sorted-data-ids)]
                                        (-> accum
                                            (update :section-ids conj section-id)
                                            (update :row-ids conj row-ids)
                                            (update :data-blob assoc section-id svm))))
                                    {:section-ids []
                                     :row-ids     []
                                     :data-blob   {}}
                                    section-v-m)
        convert-data-to-map (reduce (fn [accum m]
                                      (let [k (get m row-id-key)]
                                        (assert (not (nil? k)))
                                        (assoc accum k m)))
                                    {} row-v-m)
        merge-in-data-map (update section-and-row-ids :data-blob merge convert-data-to-map)]
    merge-in-data-map)))

andrewzhurov15:01:30

yeah, that'd be highly appreciated I'm still on the stage where trying to connect components to project ๐Ÿ˜„

ejelome15:01:31

right, I think this is the most painful from the components so far, the ListView, bec. you have to juggle with datasource, clonewithrows, and a function just to display each row, here's my attempt for 1:1 from the react native basics for listview, it's an error of course XD

(ns using-alist-view.android.core
  (:require [reagent.core :as r]))

(def ReactNative (js/require "react-native"))

(def app-registry (.-AppRegistry ReactNative))
(def list-view (r/adapt-react-class (.-ListView ReactNative)))
(def data-source (r/adapt-react-class (.-DataSource (.-ListView ReactNative))))
(def text (r/adapt-react-class (.-Text ReactNative)))
(def view (r/adapt-react-class (.-View ReactNative)))

(def ds (new data-source {:row-has-changed #(not= %1 %2)}))

(defn list-view-basics []
  [view {:style {:flex 1 :padding-top 22}}
   [list-view {:data-source (.cloneWithRows ds ["John", "Joel", "James", "Jimmy", "Jackson", "Jillian", "Julie", "Devin"])
               :render-row #([text %])}]])

(defn app-root []
  [list-view-basics])

(defn init []
  (.registerComponent app-registry "UsingAListView" #(r/reactify-component app-root)))

seantempesta15:01:09

Yeah. The ListView component was definitely designed for an imperative nested-for-loop style. Makes no sense from a pure data standpoint.

paulspencerwilliams15:01:30

@pesterhazy aparently AyncStorage should be used instead of LocalStorage

pesterhazy15:01:24

right that's what I meant @paulspencerwilliams

seantempesta15:01:49

And I recommend using react-native-storage if you are using it for caching. Setting a default expiration date saved me a bunch of time. Also the remote sync is interesting. https://github.com/sunnylqm/react-native-storage

pesterhazy15:01:54

@ejelome I think you're missing a #js: (list-data-source. #js {:rowHasChanged not=})

ejelome15:01:39

where should I put that

seantempesta16:01:42

(def ds (data-source. #js {:row-has-changed #(not= %1 %2)}))

pesterhazy16:01:32

here's a wrapper component that abstracts away the "data-source" nonsense: https://gist.github.com/pesterhazy/f6e6fca0c663a5e6bcc21a09b8b01747

ejelome16:01:16

sigh, I'm getting Object is not a constructor ๐Ÿ˜ž

ejelome16:01:47

isn't data-source. creating an object instance

seantempesta16:01:11

(def data-source (.-DataSource (.-ListView ReactNative)))

pesterhazy16:01:32

check my gist ๐Ÿ™‚

seantempesta16:01:58

yeah, @pesterhazyโ€™s version is good

ejelome16:01:12

oh, right, moment ๐Ÿ˜„

seantempesta16:01:55

Oh thatโ€™s interesting. Hadnโ€™t heard of react-native-sglistview before.

ejelome16:01:59

the sg-list-view example is very good, but I'm getting an error on the data-source part:

{:data-source (.cloneWithRows ds (clj->js ["John" "Joel" "James" "Jimmy" "Jackson" "Jillian" "Julie" "Devin"]))}

ejelome16:01:28

undefined is not an object (evaluating 'dataSource.rowIdentities)

ejelome17:01:14

it finally worked, thanks @pesterhazy ๐Ÿ˜„

ejelome17:01:35

but curious, why is {:rowHasChanged not=} and {:rowHasChanged #(not= %1 %2)} seems to have similar effect?

pesterhazy17:01:03

They should be identical

ejelome17:01:24

are the arities optional?

ejelome17:01:39

I have too many questions what happened, there's too much things going on XD

ejelome17:01:17

e.g. why the need for #js, what's the diff between #js and clj->js, and so on ๐Ÿ˜ž

pesterhazy17:01:29

yeah javascript (and clojurescript) doesn't care about arities

pesterhazy17:01:20

cljs.user=> ((fn [x y] [x y]) :a)
[:a nil]
cljs.user=> ((fn [x] [x]) :a :b)
[:a]

pesterhazy17:01:33

It's a very ... permissive culture ๐Ÿ™‚

ejelome17:01:34

ahhh, I've got used to explicitness so that somewhat confused me, so it's all really just because of how js deal with parameters

ejelome17:01:00

would it be ok if I put numerous questions, I will outline them here so that other newbies can all get a brief summary they might also encounter

ejelome17:01:45

thanks! moment

ejelome17:01:39

Here: ๐Ÿ˜„ What's the difference between (first one doesn't work, but the other does work):

(r/adapt-react-class (.-DataSource (.-ListView ReactNative)))
;; vs.
(.-DataSource (.-ListView ReactNative)))
` Why do I have to use #js, e.g.:
(data-source. #js {:rowHasChanged #(not= %1 %2)})
` Why do I have to use clj->js, e.g.:
{:dataSource (.cloneWithRows ds (clj->js ["John", "Joel", "James", "Jimmy", "Jackson", "Jillian", "Julie", "Devin"]))}
And why /as-element was even necessary for the render, e.g.:
:render-row #(r/as-element [text %])
`

pesterhazy17:01:18

ad 1) "ListView" is a React component. To use it in reagent, you need to "adapt" it into a Reagent component.

pesterhazy17:01:57

DataSource, on the other hand, is just a JavaScript class (the . means instantiation), so no adaptation necessary.

pesterhazy17:01:34

ad 2) you're passing a value to the DataSource constructor. {:a :b} is a PersistentHashMap, which a JavaScript lib won't understand. So you need to create a JavaScript object. #js is a literal to do that. (js-obj :a :b :c :d) is more explicit.

pesterhazy17:01:45

ad 3, same issue. [] creates a PersistentVector, which is not a js array. clj->js is one way to do that, better ways would be into-array or #js ["john" ...]

pesterhazy17:01:04

ad 4, as-element is necessary for the inverse reason why adapt-react-class exists. render-row expects a React render fn, i.e. a fn that returns a React element (in JS usually generated through JSX)

pesterhazy17:01:39

if you just returned [text "asdf"], that would be a PersistentVector, which JS has no clue about

pesterhazy17:01:44

makes sense?

pesterhazy17:01:41

btw these are all reagent or js interop issues, nothing particularly to do with react-native

pesterhazy17:01:20

(although I realize react-native shows these issues more than react-web)

ejelome17:01:23

ahhh, so in ... 1) it's component (r/react-adapt-class) vs. js class (.-JSClass) 2) and 3) are merely because for converting cljs data to js objects so JS can understand them and 4) is for returning a component so you need to wrap it like with as-element

ejelome17:01:21

thanks @pesterhazy, awesome answers! ๐Ÿ‘

pesterhazy17:01:45

I wanted to blog about this for a long time, I think I'll do that soon

ejelome17:01:54

I thought all this time that [text "asdf"] is already returning a component, never knew it's still a PersistentVector

ejelome17:01:23

right! thanks again ๐Ÿ˜„

ejelome17:01:36

the tricky one is #4

ejelome17:01:53

so that's how it works

paulspencerwilliams18:01:12

Regarding using asyncstorage or react-native-state to push app_db to long term storage.. Any common patterns for doing so? Ideally it would only need to be pushed on app close, and read on app initiation, but the pessimist in me thinks it might have to happen when an action updates the app_db incase battery dies etc? If this is that case, then could / should the events just write to it and not to app_db, and subs read from it. Otherwise, what's the benefit of having 2 copies around? Obviously, not using app_db is far from idiomatic when using re-frame. Feels like I'm missing something...

paulspencerwilliams18:01:44

Maybe this question would be best placed in #re-frame