One of the techniques I like to use while building Reagent/re-frame apps is to encapsulate common view logic in reusable containers. For the purpose of this article let’s define a container as a Reagent component which:

Accepts other view components as arguments

Can wrap given component with additional markup

Can have its own state

Can have their own lifecycle

Can be composed

In this article, I’ll show several examples that highlight all listed aspects of containers. I’ve also published a sample application with all examples at https://kishanov.github.io/reagent-containers-demo. The source code of demo application is also available at https://github.com/kishanov/reagent-containers-demo

Markup Containers

The simplest container that I can think of is a component that can wrap another component with some static markup:

(defn basic-container [view-component]

[:div view-component])

And it can be used anywhere in the view code like this:

[basic-container

[sample-component]]

This container is quite useless but it shows the main technique: passing another component as an argument and then wrapping it with some markup. Let’s create another container that uses Semantic UI’s grid to wrap any component in a centered column that takes 50% of the grid space and uses some unobtrusive colors to distinguish between the grid and the column:

(defn centered-column-container [view-component]

[:div.ui.grid

{:style {:background "purple"}}

[:div.two.column.centered.row

[:div.column

{:style {:background "cyan"}}

view-component]]])

And our sample component can be wrapped into it the same way as previously:

[centered-column-container

[sample-component]]

In the ®ion it looks like this:

Parametrizing Components

Containers are defined as Reagent components, which are just plain old ClojureScript functions treated specially by Reagent. This means that we can add additional arguments that

For example, it’s easy to make a generic container that represents Semantic UI’s Message component like this:

(defn message [icon-class message-class header description]

[:div.ui.icon.message

{:class message-class}

[:i.icon

{:class icon-class}]

[:div.content

[:div.header header]

[:div description]]])

This container can be used as is, or we can created several pre-defined containers using partial 's:

(def error-message (partial message "warning sign" "negative"))

(def success-message (partial message "check circle" "positive"))

(def loading-message (partial message "notched circle loading" "default"))

After that error-message can be used anywhere with just 2 arguments: header and description . Notice that description can be any component, not just text:

[error-message

"Oops"

[:div "Something went wrong. Please, "

[:a {:href "#"} "contact administrator"]]]

Other examples of message component can be found at https://kishanov.github.io/reagent-containers-demo/#/param-containers

Stateful Containers

Some containers need to keep their own state to determine when/how to render components that they wrap. The first example that comes to mind is any kind of navigation menu: tabs/vertical menu/wizard steps, etc. This type of component needs to keep track of active tab in order to determine which selected menu item to highlight and which corresponding component to render.

One way to implement such container would be to create a container that accepts a data structure which can describe all required aspects of tabs. The data structure that I’ll be using looks like this:

(list {:key :home

:label "Home"

:component [:div "Home panel"]}

{:key :messages

:label "Messages"

:component [:p "You have " [:strong 0] " unread messages!"]}

{:key :friends

:label "Friends"

:component [:div.ui.list

[:div.item "Friend 1"]

[:div.item "Friend 2"]]})

With this list passed as an argument to nav-tabs component we just need to implement simple handling logic which iterates over all of the tab definitions and based on currently selected tab renders :component and highlights appropriate tab as active .

To keep track of active tab we can either use reagent/atom or have a corresponding re-frame subscription/event handler. Here is Reagent example:

(defn nav-tabs [menu-class tabs-def]

(let [active-tab (reagent/atom (:key (first tabs-def)))]

(fn []

[:div

(into [:div.ui.menu

{:class menu-class}]

(map (fn [{:keys [key label]}]

[:a.item

{:class (when (= @active-tab key)

"active")

:on-click #(reset! active-tab key)}

label])

tabs-def))

^{:key @active-tab}

[:div (->> tabs-def

(filter #(= @active-tab (:key %)))

(first)

:component)]])))

And use it like this:

[nav-tabs

"tabular"

(list {:key :home

:label "Home"

:component [:div "Home panel"]}

{:key :messages

:label "Messages"

:component [:p "You have " [:strong 0] " unread messages!"]}

{:key :friends

:label "Friends"

:component [:div.ui.list

[:div.item "Friend 1"]

[:div.item "Friend 2"]]})]

Interesting consequence is that containers (even stateful) can be nested. I can extend nav-tabs example to use 2nd level of navigation for Messages tab:

[nav-tabs

"tabular"

(list {:key :home

:label "Home"

:component [:div "Home panel"]}

{:key :messages

:label "Messages"

:component [nav-tabs

"secondary"

(list {:key :inbox

:label "Inbox"

:component [:div "No new messages"]}

{:key :spam

:label "Spam"

:component [:strong "Enlarge your EGO"]})]}

{:key :friends

:label "Friends"

:component [:div.ui.list

[:div.item "Friend 1"]

[:div.item "Friend 2"]]})]

Time to use “Turtles all the way down” cliche. A demo for this step is available at https://kishanov.github.io/reagent-containers-demo/#/stateful-containers

Containers which rely on Component Lifecycle

One of the techniques in React/Reagent world that can be easily and unnecessarily abused is the ability to use lifecycle methods in order to properly set up or clean component’s state. In conjunction with containers approach, it can lead to the creation of quite complex containers that do interesting things. Here are several example use cases:

Fetch data via HTTP. A container can be used to initialize series of API call and render loader spinner until API responses have been completed (and also render an error message if one of these API calls has failed or returned an error code)

spinner until API responses have been completed (and also render an error message if one of these API calls has failed or returned an error code) Wrap form body with a component that handles “Submit” button behavior and corresponding form state (do the proper events dispatch when submit is clicked, disable form during submission, change the state of “Submit” button during submission, handle the result of the submission)

Wrap existing JS components.

The complete code for each of these examples will be quite complex (primarily due to it requires dealing with other aspects of application development like making HTTP calls or processing forms), so I’ll just show a scaffold of the code that’s main purpose is to communicate the idea of how things like this can be achieved.

For data fetching container, we’ll assume that there is a data structure api-calls that our application understands how to process. In the most naive implementation it can be just a list of URI to which GET request should be made. In this case the container will need to utilize :component-will-mount in order to trigger API calls, have a state machine that looks at API responses in order to determine if it should render view-component and define a :component-will-unmount hook to cleanup responses from app-db :

(defn fetch-data-container [api-calls view-component]

(reagent/create-class

{:component-will-mount

#(re-frame/dispatch [::events/do-http-calls api-calls])



:component-will-unmount

#(re-frame/dispatch [::events/clear-http-responses api-calls])



:component-function

(fn []

(case @(re-frame/subscribe [::subs/responses-fetch-state api-calls])

::model/resource-not-found

[message/error-message

"404"

"Resource with given ID is not found"]



::model/server-error

[message/error-message "Server Error"]



::model/spec-error

[message/error-message

"Schema Error"

"Some API responses doesn't match the schema that UI expects"]



::model/loading

[message/loading-message]



::model/success

view-component))}))

Such container can wrap any part of the view that requires data from the back-end. Notice that it can either be one component to wrap the whole page and track all API calls that should be made for all of the components on this page, or it can wrap each individual component (in this case the page might look like a collage of spinners until required data has arrived). A sample usage (again, pseudocode) can look like this:

[fetch-data-container

(list "/api/resources1" "/api/resources1")

[:div

@(re-frame/subscribe [::subs/api-response-for-resource1])]]

Composing Containers

In real-world application, components are often wrapped in multiple containers. Imagine typical “Edit” form for the REST resource: it will be probably wrapped into some layout container, then into form state management container, then into data fetching container (that fetches “details” API call in order to init “edit” form) and then it will be probably wrapped into modal layout container if we want edit logic to be available in a modal. In order for this Matryoshka doll to be easily composable I prefer to follow several conventions:

For every container, have view-component as the last argument to component function

as the last argument to component function Do not pass arguments that can be changed via props (for the stateful components it might lead to entertaining debugging and going into the scary land of component-did-mount or should-component-update )

A good litmus paper test for the composability of containers is when they can be glued together as a single container via Clojure’s comp function. Our hypothetical component from the previous example can be split into two hypothetical components with (presumably) lower cyclomatic complexity: fetch-data-container and wait-for-api-responses containers like this:

(defn wait-for-api-responses-container [api-calls view-content]

(case @(re-frame/subscribe [::subs/responses-fetch-state api-calls])

::model/resource-not-found

[message/error-message

"404"

"Resource with given ID is not found"]



::model/server-error

[message/error-message "Server Error"]



::model/spec-error

[message/error-message

"Schema Error"

"Some API responses doesn't match the schema that UI expects"]



::model/loading

[message/loading-message]



::model/success

view-content))







(defn fetch-data-container [api-calls view-component]

(reagent/create-class

{:component-will-mount

#(re-frame/dispatch [::events/do-http-calls api-calls])



:component-will-unmount

#(re-frame/dispatch [::events/clear-http-responses api-calls])



:component-function

(identity view-component)}))

After this artificial decoupling this components can meet each other again in the following usage:

(let [api-calls (list "/api/resources1" "/api/resources1")]

[fetch-data-container

api-calls

[wait-for-api-responses-container

api-calls

[:div

@(re-frame/subscribe [::subs/api-response-for-resource])]]])

Or we can glue this components together using comp for “style points” (and more WTFs in pull request comments):

(let [api-calls (list "/api/resources1" "/api/resources1")]

[(comp (partial wait-for-api-responses-container api-calls)

(partial fetch-data-container api-calls))

[:div

@(re-frame/subscribe [::subs/api-response-for-resource])]])

Clojure is both famous and notorious for being programmer’s play-doh and the possibilities of combining together multiple containers becomes yet another yak to be shaven.