Reagent performance issue when passing atom as a function parameter - react-native

I work on the react-native application using Clojurescript re-frame and reagent. I have one text input component, and have two versions of the code:
Version 1: input text is a separate component, and state atom is passed as an argument, the same as the recommended in the reagent library docs and examples.
(defn todo-input [value]
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
)
(defn todo-screen []
(let [value (r/atom nil)]
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[todo-input value]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]
[todos]
]))
Version 2: everything in one component.
(defn todo-screen []
(let [value (r/atom nil)]
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]
]]
[todos]]))
The issue is that first version has a performance issue while typing, since there's a big delay and flickering when trying to type fast. Version 2 doesn't have any issues, and I can type as fast as I can.
According to the reagent documentation, passing r/atom as a parameter should not incur any performance issues.
Am I doing something wrong here? What would be the best way to avoid performance penalty here.
This is a small example, and having one component instead of two is not a big deal, but splitting one big to multiple smaller components in a good praxis. State here is local to the component, and I don't want to use re-frame for it.

re-frame/dispatch puts your events in a queue for re-frame to process, so there can be a slightly delay before it actually goes through and your change will be there.
Sounds like you're experiencing the same issue stated here: https://github.com/day8/re-frame/issues/368
So one work around is to use re-frame.core/dispatch-sync which forces re-frame to handle the event directly and synchronously. You might have to also add a call to reagent.core/flush to force re-render the component. I haven't needed flush before when building web clients, but React Native seems to work differently.
You can read more about these two functions here:
https://github.com/day8/re-frame/blob/master/src/re_frame/router.cljc#L251-L263
https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/batching.cljs
Mentioned in the issue above is also https://github.com/Day8/re-com that supposedly works around the issue somehow, but I didn't take a closer look at that.
Your solution #2 is not wrong either, it just gives you a different way of working. So if you need the data in your app-db to update on every keypress for example, only something more like #1 will work. Or using solution #2 but passing in the atom as an argument to your component.

Both of your versions have an issue. You should use Form-2 type components when using local state. Like this:
(defn todo-screen []
(let [value (r/atom nil)]
(fn []
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[todo-input value]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]
[todos]])))
More info about Form-2 here.
Or you could use the r/with-let instead. More info about with-let.
Regarding your original question, you could have a compromise between both your versions, and extract the input and submit button into a separate component:
(defn todo-input-container [on-press]
(r/with-let [value (r/atom nil)]
[rn/view {:style (styles :textInputContainer)}
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
[rn/touchable-opacity
{:on-press (fn []
(on-press #value)
(reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]))
(defn todo-screen []
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[todo-input-container (fn [value] (rf/dispatch [:add-todo value]))]
[todos]])

Looks like the issue here is that reagent doesn't support well components with the controlled inputs due to its async nature.
Controlled input (via :value) should be avoided, or worked around by forcing component update immediately after changing :value.
See reagent issue and explanation for more details.

Related

How to use flat-list in re-natal?

I have the following flatlist:
[flat-list {
:data [{:name "a"}
{:name "b"}
{:name "c"}]
:render-item (fn [item] [text (:name item)])
:key-extractor #(random-uuid)
}
]
But it's not working and gives me:
Invariant Violation: Objects are not valid as a React child (found: object with keys {name, id, class}). If you meant to render a collection of children, use an array instead.
What am I doing wrong?
-- EDIT --
Now I have the following:
[flat-list {
:data [{:name "a"}
{:name "b"}
{:name "c"}]
:render-item (fn [item-]
(r/reactify-component
[text (:name item-)]
))
:key-extractor #(:name %)
:content-container-style {:align-items "center"}
}
]
But I get the error: Functions are not valid as a React child. This may happen if you returns a Component instead of <Component/> from render. Or maybe you meant to call this function rather than return it.
Using as-element instead doesn't give the error but also doesn't render the text components.
It's just because renderItem waits a react component
:render-item (fn [item]
(r/reactify-component [text (:name item)]))
:render-item (fn [item]
(r/as-element [text (:name item)]))

Clojure tools.analyzer, don't expand macros

What is the best way to avoid expanding macros when creating ast's using tools.analyzer.jvm/analyze
This is an example of the information I am currently collecting:
(map of function name to dependency set)
{some-function
#{{:name load-order-ns-file-maps, :ns #namespace[clj-graph.core]}
{:name *logger-factory*, :ns #namespace[clojure.tools.logging]}
{:name analyze, :ns #namespace[clojure.tools.analyzer.jvm]}
{:name make-dir-tracker, :ns #namespace[clj-graph.core]}
{:name enabled?, :ns #namespace[clojure.tools.logging.impl]}
{:name read-all, :ns #namespace[clj-graph.core]}
{:name get-logger, :ns #namespace[clojure.tools.logging.impl]}
{:name traverse-expr, :ns #namespace[clj-graph.core]}
{:name log*, :ns #namespace[clojure.tools.logging]}
{:name track-reload,
:ns #namespace[clojure.tools.namespace.reload]}
{:name examine-form, :ns #namespace[clj-graph.core]}}}
The actual code calls log/info but as this is macroexanded, I can never capture the the name and ns in which it was declared - instead I get the macro expansion giving me:
:name *logger-factory*, :ns #namespace[clojure.tools.logging]
:name enabled?, :ns #namespace[clojure.tools.logging.impl]
:name get-logger, :ns #namespace[clojure.tools.logging.impl]
:name log*, :ns #namespace[clojure.tools.logging]
As I am building a dependency graph, ideally I just want to find the endpoints, ie :name info :ns #namespace[clojure.tools.logging].
Reading the documentation for analyzer.jvm/analyze it gives an example as:
(analyze form env {:bindings {#'ana/macroexpand-1 my-mexpand-1}})
but when I try this, ie:
(defn ^:dynamic my-expand-1 [form] form)
(ana/analyze
'(defn prnt [xs] (my-pre-defined-macro xs))
(ana/empty-env)
{:bindings {#'ana/macroexpand-1 my-expand-1}})
I get the error
IllegalStateException Can't dynamically bind non-dynamic var:
clojure.tools.analyzer.jvm/macroexpand-1 clojure.lang.Var.pushThreadBindings (Var.java:320)
If macros were not expanded, you couldn't get any useful information, because what a macro expands into is completely opaque to you. Perhaps someone has written this function:
(defn foo [x]
(-> x
inc
println))
If you don't expand the -> macro, you will not find out that foo depends on inc and println. And finding out that it depends on -> is not very interesting.
I solved this for specific macros by providing implementations of macroexpand-1 and parse. My macroexpand-1 implementation ignores the specific macros I want to parse myself (by simply returning the form) and my parse implementation provides custom parsing. You can pass custom method bindings in the options to analyze:
For example, to provide custom parsing of the binding macro:
(require '[clojure.tools.analyzer.jvm :as ja])
(require '[clojure.tools.analyzer :as ana])
(defn my-parse [[op & _ :as form] env]
(if (= op 'binding)
(merge (ana/parse-let* form env)
{:op :unexpanded-dynamic-binding})
(ja/parse form env)))
(defn binding-form? [o] (and (seq? o) (= (first o) 'binding)))
(defn my-macroexpand-1
([form] (my-macroexpand-1 form (ja/empty-env)))
([form env]
(if (binding-form? form)
form
(ja/macroexpand-1 form env))))
(ja/analyze
'(binding [*clojure-version* 1] *clojure-version*)
(ja/empty-env)
{:bindings {#'ana/parse my-parse
#'ana/macroexpand-1 my-macroexpand-1}})

Test pre-requisites for tabular tests; how does tabular work?

Let's say I am attempting to test an api that is supposed to handle presence or absence of certain object fields.
Let's say I have tests like so:
(def without-foo
{:bar "17"})
(def base-request
{:foo "12"
:bar "17"})
(def without-bar
{:foo "12"})
(def response
{:foo "12"
:bar "17"
:name "Bob"})
(def response-without-bar
{:foo "12"
:bar ""
:name "Bob"})
(def response-without-foo
{:bar "17"
:foo ""
:name "Bob"})
(facts "blah"
(against-background [(external-api-call anything) => {:name => "Bob"})
(fact "base"
(method-under-test base-request) => response)
(fact "without-foo"
(method-under-test without-foo) => response-without-foo)
(fact "without-bar"
(method-under-test without-bar) => response-without-bar))
This works as you would expect and the tests pass. Now I am attempting to refactor this using tabular like so:
(def request
{:foo "12"
:bar "17"})
(def response
{:foo "12"
:bar "17"
:name "Bob"})
(tabular
(against-background [(external-api-call anything) => {:name "Bob"})]
(fact
(method-under-test (merge request ?diff) => (merge response ?rdiff))
?diff ?rdiff ?description
{:foo nil} {:foo ""} "without foo"
{} {} "base case"
{:bar nil} {bar ""} "without bar")
Which results in:
FAIL at (test.clj:123)
Midje could not understand something you wrote:
It looks like the table has no headings, or perhaps you
tried to use a non-literal string for the doc-string?
Ultimately I ended up with:
(tabular
(fact
(method-under-test (merge request ?diff) => (merge response ?rdiff) (provided(external-api-call anything) => {:name "Bob"}))
?diff ?rdiff ?description
{:foo nil} {:foo ""} "without foo"
{} {} "base case"
{:bar nil} {bar ""} "without bar")
Which passes. My question is. How does the tabular function differ from the facts function, and why does one of them accept an against-background while the other blows up?
You need to have following nesting if you want to establish background prerequisites for all your tabular based facts:
(against-background [...]
(tabular
(fact ...)
?... ?...))
For example:
(require '[midje.repl :refer :all])
(defn fn-a []
(throw (RuntimeException. "Not implemented")))
(defn fn-b [k]
(-> (fn-a) (get k)))
(against-background
[(fn-a) => {:a 1 :b 2 :c 3}]
(tabular
(fact
(fn-b ?k) => ?v)
?k ?v
:a 1
:b 3
:c 3))
(check-facts)
;; => All checks (3) succeeded.
If you want to have a background prerequisite per each tabular case you need to nest it as following:
(tabular
(against-background [...]
(fact ...))
?... ?...)
It's important to have the table just under tabular level, not nested in against-background or fact.
For example:
(require '[midje.repl :refer :all])
(defn fn-a []
(throw (RuntimeException. "Not implemented")))
(defn fn-b [k]
(-> (fn-a) (get k)))
(tabular
(against-background
[(fn-a) => {?k ?v}]
(fact
(fn-b ?k) => ?v))
?k ?v
:a 1
:b 2
:c 3)
(check-facts)
;; => All checks (3) succeeded.
In your code it looks like the tabular data is not positioned correctly (parentheses, brackets and curly braces are not balanced correctly so it's impossible to say what exactly is incorrect).

rails3 content_tag with static attributes

The following is being properly generated into HTML code
<%= content_tag(:span, (t 'hints.h'), :class => "has-tip", :title => (t 'hints.s') ) %>
But I am trying to generate
<span data-tooltip aria-haspopup="true" class="has-tip" title="title bla bla">translated h</span>
and have found no way to generate these span attributes data-tooltip aria-haspopup="true" They cannot be part of the options hash given one has only a name... and the second one has a dash which impedes from defining it as a symbol :aria-haspopup
I suggest that you use the following:
content_tag(:span, t('hints.h'), :class => 'has-tip', :title => t('hints.s'), :'aria-haspopup' => true, :'data-tooltip' => '')
Note that you can use the dash character in symbols if you enclose them in quotes.
The data attribute you could also specify as nested hash like :data => {:tooltip => ''} instead of :'data-tooltip' => '', use whatever you prefer.
As for the boolean attribute data-tooltip, setting the value to an empty string is as good as omitting it (and your best option with Rails 3 ;)). See also:
http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#boolean-attributes

Using nested attributes to easily select associations in a form

I am trying to create a nested attribute form to create a model which is primarily an association "connector" between two other models. In my case, the models represent books, awards, and the "connector" model book_awards. When I am editing a book, I want to be able to quickly select which awards it has won.
I've been using
http://railscasts.com/episodes/196-nested-model-form-part-1
to help me get started, but I'm afraid I'm pretty much stuck.
Another SO question which seems similar is
accepts_nested_attributes_for with find_or_create? Unfortunately, it's also not quite what I'm doing and I haven't been able to adapt it.
My models look like this. Each model has additional attributes and validations etc, but I've removed them for clarity.
class Book < ActiveRecord::Base
has_many :book_awards
accepts_nested_attributes_for :book_awards, :allow_destroy => true
end
class Award < ActiveRecord::Base
has_many :book_awards
end
class BookAward < ActiveRecord::Base
belongs_to :book, :award
end
In my book controller methods for edit and new, and the failure cases for create and update I have a line #awards = Award.all.
In my view, I would like to see a list of all awards with check boxes next to them. When I submit, I would like to either update, create, or destroy a book_award model. If the check box is selected, I would like to update an existing model or create a new one if it doesn't exist. If the check box isn't selected, then I would like to destroy an existing model or do nothing if the award never existed. I have a partial for book_awards. I'm not sure if the check box selector should be in this partial or not.
I think my check box will be my hook to :_destroy but with its polarity reversed. I think something like this will basically do it:
= f.check_box :_destroy, {}, 0, 1
Currently, I have this in my partial but I'm not sure where it really belongs.
Next comes my view which currently doesn't work, but maybe it will help demonstrate what I'm trying to do. I loop through the awards and use a fields_for to set nested attributes for anything that already exists. It's horribly ugly, but I think it somewhat works. However, I don't really know how to get started with the else case.
= f.label :awards
- #awards.each do |a|
- if f.object.awards && f.object.awards.include?(a)
= f.fields_for :book_awards, f.object.book_award.select{|bas| bas.award == a } do |ba|
= render 'book_awards', :f => ba, :a => a
- else
= fields_for :book_awards do |ba|
= render 'book_awards', :f => ba, :a => a
I would prefer the awards to be listed in the same order each time (my #awards assignment in the controller will probably specify the order) as opposed to listing the existing awards first or last.
I hate to answer my own question, but I finally figured out something which works. The first thing I needed to do was to update the "new" case based on the crazy object which was included in the railscast. Next, I needed to manually set the :child_index. Finally, I needed to manually set the :_destroy check box appropriately.
.field
= f.label :awards
- #awards.each_with_index do |a,i|
- if exists = (f.object.awards && f.object.awards.include?(a))
- new_ba = f.object.book_awards.select{|s| s.award == a}
- else
- new_ba = f.object.class.reflect_on_association(:book_awards).klass.new
= f.fields_for :book_awards, new_ba, :child_index => i do |ba|
= render 'book_awards', :f => ba, :a => a, :existing => exists
My partial looks like this:
.field
= f.check_box :_destroy, {:checked => existing}, 0, 1
= f.label a.name
= f.hidden_field :award_id, :value => a.id
= f.label :year
= f.number_field :year
It's not horribly pretty, but it seems to do exactly what I wanted.