Web Development with Clojure, Third Edition: author routing fails to re-render header when clicking on "My posts" (p205/p210)

There is a subtle bug introduced by the author component:

If I navigate to /user/test to view the posts of the user test, and then click on “My Posts” (logged in as a user that is not test) then the messages are correctly re-rendered but the Header remains “Messages By test”, rather than "Messages By "

I have two solutions that appear to fix this but I’m not clear on the implications of each choice as I’ve not really grokked what’s happening behind the scenes yet…

  • Passing {{{:keys [user]} :path} :parameters} through to the inner function:
(defn author [{{{:keys [user]} :path} :parameters}]
  (let [messages (rf/subscribe [:messages/list])]
    (fn [{{{:keys [user]} :path} :parameters}]
      [:div.content>div.columns.is-centered>div.column.is-two-thirds
       [:div.columns>div.column
        [:h3 "Messages By " user]
        (if @(rf/subscribe [:messages/loading?])
          [messages/message-list-placeholder]
          [messages/message-list messages])]])))
  • Remove the closure altogether:
(defn author [{{{:keys [user]} :path} :parameters}]
  (let [messages (rf/subscribe [:messages/list])]
      [:div.content>div.columns.is-centered>div.column.is-two-thirds
       [:div.columns>div.column
        [:h3 "Messages By " user]
        (if @(rf/subscribe [:messages/loading?])
          [messages/message-list-placeholder]
          [messages/message-list messages])]]))
4 Likes

Good catch! This is a very common bug with reagent, and often slips by even experienced devs.
In this case, we should remove the closure altogether, because we aren’t making use of it to initialize local state. However, if we were, your first solution would work.

What’s happening behind the scenes is the following:

The author component is initialized with the user test and returns the inner function, which reagent then uses to re-render author as necessary. We’ve closed over user, so it will never change.

We navigate to another user, so the inner function is called again with the updated arguments (which it ignores because of our mistake) and is unchanged, so it stays as is.

Then, the subscription :messages/loading? changes, so it is called yet again, and this time it does change, so it re-renders with the placeholder.

The :messages/list subscription and the :messages/loading? subscription change simultaneously, so author re-renders and mounts its child component messages/message-list which in turn responds to :messages/list and displays the updated messages.

Hope that helps!

This will be addressed in the next release. Thanks for reading!

2 Likes