0

I use an (if) condition as form-2 render-function in this way:

(defn bro [dex]
      (let [yo (inc dex)]
           (if true
               [:div (str yo)])))

instead of this way:

(defn bro [dex]
      (let [yo (inc dex)]
           (fn [dex]
               (if true
                   [:div (str yo)]))))

Is a problem if i use an (if) statement instead of an (fn) function? And what happens when the statement gone false? The render function returns with nil?

2 Answers2

2

No, this won't work as you expect. To understand the difference, consider the following two components:

(defn test-component-if []
  (let [a (atom 1)
        _ (.log js/console "let in -if")]
    (if (odd? @a)
      [:div
       [:p "odd"]
       [:button {:on-click #(swap! a inc)}
        "inc"]]
      [:div
       [:p "even"]
       [:button {:on-click #(swap! a inc)}
        "inc"]])))

(defn test-component-fn []
  (let [a (atom 1)
        _ (.log js/console "let in -fn")]
     ;; I dub thee test-inner
    (fn []
      (if (odd? @a)
        [:div
         [:p "odd"]
         [:button {:on-click #(swap! a inc)}
          "inc"]]
        [:div
         [:p "even"]
         [:button {:on-click #(swap! a inc)}
          "inc"]]))))

test-component-fn works as expected, while test-component-if does not. Why is that? Well when a reagent component can return one of two things (I'm ignoring "type-3" components, as that hooks into react knowledge). It can return

  1. a vector
  2. another function

If it returns a vector, the function itself becomes the render function, in our case test-component-if. When it returns a function, the function that was returned, not the original function, is the render function. In this case, what I have dubbed test-inner

When Reagent calls a render function, it tracks the atoms that function accesses, and whenever that atom changes it calls the render function. So what happens when we use test-component-if?

  1. Reagent calls test-component-if
  2. Our let clause binds a new atom a to 1.
  3. A vector is returned
  4. We click the button
  5. The atom a is incremented
  6. Reagent sees the change to a and calls test-component-if
  7. Our let clause binds a new atom a to 1. (A different atom than our previous a)
  8. Ooops!

So a is always 1. You can verify this by looking at the console, and seeing that the message is printed every time you click the button.

Now how about test-component-fn?

  1. Reagent calls test-component-fn
  2. Our let clause binds a new atom a to 1.
  3. test-component-fn returns test-inner which closes over a
  4. Reagent calls test-inner
  5. We click the button
  6. a is incremented
  7. Reagent sees the change to a and calls test-inner
  8. Repeat as many times as you want.

You can verify that let only gets executed once again on the console. Click the button as many times as you want, the message will only be printed when it's first rendered.

In terms of an if without an else clause, this will indeed return nil. It's convention to use when instead of if in such cases, which makes it clear the lack of an else is intended. It also has the advantage of including an implicit do. As for what Reagent will do when it encounters that nil, in most cases it will silently remove it and display nothing.

Walton Hoops
  • 864
  • 4
  • 14
0

Is a problem if i use an (if) statement instead of an (fn) function?

I think you meant to use an if inside an fn function, but either way it's not a problem.

And what happens when the statement gone false? The render function returns with nil?

Reagent handles these gracefully, a nil will be skipped (no corresponding child is created). If you see the TODOs app example in the official docs, you'll see source has code like the following:

(when (pos? done)
       [:button#clear-completed {:on-click clear-done}
        "Clear completed " done])]))

In this case, if done is not a positive number, the return value of this expression is nil and the button to clear the completed tasks is simply not added to the DOM.

Denis Fuenzalida
  • 3,271
  • 1
  • 17
  • 22