Search code examples
clojureclojurescriptom

What is the difference between using defmulti and defn as a function of om/build-all?


  • om "0.8.0"

I have recently started learning om by using examples codes of om repository. Now, I am checking multi example and I can understand the behavior of this program.

After I clicked '+' button,

  • First, "Even(or Odd) widget unmounting" is printed.
  • Next, "Odd(or Even) widget mounting" is printed.

But when I added following code
(just change even-odd-widget defmulti code to defn code)

(defn test-widget
  [props owner]
  (reify
    om/IWillMount
    (will-mount [_]
      (println "Test widget mounting"))
    om/IWillUnmount
    (will-unmount [_]
      (println "Test widget unmounting"))
    om/IRender
    (render [_]
      (dom/div nil
        (dom/h2 nil (str "Test Widget: " (:my-number props)))
        (dom/p nil (:text props))
        (dom/button
          #js {:onClick #(om/transact! props :my-number inc)}
          "+")))))

and tried to use this function instead of test-widget, as the result, there was no print message...

So what is the difference between defmulti and defn in this case? Is this bug or correct behavior?

Thanks in advance.


Solution

  • The protocol methods om/IWillMount and om/IWillUnmount are only invoked once when the component is mounted/unmounted. It is not invoked on every render and therefore a log message will not be produced when you click the '+' button if it does not result in a component being mounted/unmounted. The use of multimethods has no impact on this behavior.

    The reason that you are seeing repeated log statements when using the multimethod version is that different components are being returned on every click and therefore the components are also mounted/unmounted every time you click '+', while with the plain function a single component is mounted and remains mounted every time you click '+'.

    The log messages you are producing will appear in the developer console of the browser for both your plain function and multimethod versions, but only when the component mounts/umounts and not on every render.

    Clicking the '+' button will trigger a re-render in the existing mounted component in the plain function scenario. If you want to log on every render of the component, you would need to do the following:

    (defn test-widget
      [props owner]
      (reify
        om/IWillMount
        (will-mount [_]
          (println "Test widget mounting"))
        om/IWillUnmount
        (will-unmount [_]
          (println "Test widget unmounting"))
        om/IRender
        (render [_]
          (.log js/console "Rendering test widget!")
          (dom/div nil
                   (dom/h2 nil (str "Test Widget: " (:my-number props)))
                   (dom/p nil (:text props))
                   (dom/button
                    #js {:onClick #(om/transact! props :my-number inc)}
                    "+")))))
    
    (defn app [props owner]
      (reify
        om/IRender
        (render [_]
          (apply dom/div nil
            (om/build-all test-widget (:widgets props))))))