The re-frame library built on ClojureScript allows you to create dynamic frontend applications leveraging React. As a functional programmer, you’ll find it easy to work with UI components in re-frame because they are pure functions from the state to the Hiccup.
We can use React’s lifecycle methods in re-frame. Lifecycle methods allow us to execute code at various phases of a component’s lifecycle. Luckily, React lifecycle methods are easier to use in re-frame because of ClojureScript’s elegant and compact syntax.
In this tutorial, we will create a form in a re-frame application and learn about the different React lifecycle methods you can use to monitor and manage your application at various phases, with practical examples along the way. We will cover:
Prerequisites for following along with this tutorial include:
Leiningen takes care of automation and other nitty-gritty in your Clojure project so you can focus on your code. If you’re on Mac, you can use Homebrew to install Leiningen by executing this command on the terminal:
brew install leiningen
Not using Homebrew? Check out the official Leiningen docs for a list of supported package managers and the installation command for each package.
After installing Leiningen, the next thing you need to do is use re-frame-template
to scaffold a re-frame application. To do this, execute the following command on your terminal:
$ lein new re-frame <app-name>
Before running the command, replace <app-name>
with whatever name you want to give your app. Make sure you avoid using cljs
as the application name to avoid future conflicts.
Wait until the application’s source code is generated, then open the application in VS Code. It’s going to ask you what build to connect to and give you the following choices: app
, node-repl
, or browser-repl
. Select the app
option to start the build process.
Once the build process is complete, launch your web browser and navigate to http://localhost:8280
to view the app.
Before we proceed, I highly recommend installing two extensions — Calva and Rainbow Brackets — in VS Code. Both give you syntax highlighting, ClojureScript REPL, and many other features.
In this section, we’ll create a simple form inside our Clojure app, and we’ll style it using Bulma CSS. I chose Bulma to demonstrate how easy it is to convert HTML markup into the Hiccup syntax of ClojureScript.
To use Bulma in your application, simply add its CDN link to your app. Here’s the code:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
Copy this link element to your clipboard, go into the index.html
file in your project, and then paste the link between the opening and closing <head>
tags.
Now that Bulma is included in our app, the next task is to create the form.
If you look under the “General Form” section of the Bulma documentation, you’ll find sample HTML for text inputs, dropdown menus, and buttons. We’ll take these HTML markups and convert them to their Hiccup equivalent.
Let’s start with the text input for collecting the user’s name:
<div class="field"> <label class="label">Name</label> <div class="control"> <input class="input" type="text" placeholder="Your name"> </div> </div>
Below is its equivalent in ClojureScript with Bulma classes for styling. Copy the code below and paste it into your view.cljs
file:
(defn text-input [id label] [:div.field [:label.label "name"] [:div.control [:input.input {:type "text" :placeholder "Text input"}]]])
Next is the dropdown list, which, in HTML, is a <select>
element with multiple <option>
elements:
<div class="field"> <label class="label">Topic</label> <div class="control"> <div class="select"> <select> <option>Choose one</option> <option>Option one</option> </select> </div> </div> </div>
And here’s its equivalent in ClojureScript — paste this code below the previous text input:
(defn select-input [] [:div.field [:label.label "Topic"] [:div.control [:div.select [:select [:option "Choose one"] [:option "Option one"]]]]])
Finally, we have a simple button for submitting the form, styled using Bulma CSS classes.
Here’s the HTML markup:
<div class="control"> <button class="button is-link">Submit</button> </div>
Below is the HTML markup’s equivalent in ClojureScript — paste this code below the code for the dropdown list:
(defn main-panel [] (let [name (re-frame/subscribe [::subs/name])] [:div.section [text-input] [select-input] [:button.button.is-primary "Save"]]))
Now you have created a form comprising a name field, dropdown list, and “submit” button. Save your view.cljs
file and navigate to http://localhost:3230
on your browser. You should see a form similar to the below:
Let’s now turn our attention to the various lifecycle methods available to us in re-frame.
React components have ten lifecycle methods in total. React invokes these methods on your component when their respective events are triggered.
For example, when a component’s data changes and that component is consequently rerendered, if you implemented the componentDidUpdate
method, it will be immediately invoked.
The re-frame library allows you to use React lifecycle methods in Clojure apps, but things are different.
re-frame separates the data fetching functionality from the component and transforms it into pure functions from input to rendering. In essence, data is still retrieved from the server, but it is done from outside the components.
Reagent, which is used by re-frame, provides us with two methods for creating components that will serve us 90 percent of the time. Those are the Form-1 and Form-2 components, which are both based on functions.
Form-1 is for components that only require state from the database, while Form-2 is for components that require data from the local state as well. There is also a third group of components called Form-3 components. Only Form-3 components can use React lifecycle methods.
We use Form-3 components when we need to manipulate the DOM in ClojureScript. Let’s take a closer look at the structure of a Form-3 component.
The only way to access lifecycle methods in re-frame is by using Form-3 components. Let’s take a look at the typical structure of a Form-3 component:
(defn form3-component [a b c] (let [state (reagent/atom {})] ;; you can include state (reagent/create-class {:component-did-mount (fn [] (println "Just mounted")) ;; ... add other methods here ;; give your component a display name for inclusion in error messages :display-name "form3-component" ;; note this method's keyword :reagent-render (fn [a b c] [:div {:class c} [:i a] " " b])})))
As you can see in the example above, the Form-3 component function makes a call to reagent.core/create-class
and returns the value of that call. You use create-class
to specify the lifecycle methods you’d like to implement, with :reagent-render
being the only required method.
Finally, don’t forget to define a :display-name
to be printed in the console in the case of an error, which will help in the debugging process.
There are nine different React lifecycle methods, each with specific and often complex uses. In this section, we will explore the four React lifecycle methods you would use in re-frame, including when they are called, their use cases, and an example of each.
:component-did-mount
lifecycle method:component-did-mount
is called immediately after a component is mounted, or inserted into the DOM tree. It’s invoked once on the client side and once on the server side.
Use cases for the :component-did-mount
lifecycle method include interacting with external APIs, adding event listeners, and setting up subscriptions. It’s also used for any updates that need to happen before the component is rendered.
Here’s an example that just prints a message to confirm that the component is mounted:
(defn my-component [props] (reagent/create-class {:component-did-mount (fn [this] (println "Component mounted!")) :render (fn [this] [:p "My component"])}))
To use this lifecycle method in the case of a form like the one we created at the start of this article, you can make an API call to an external service in component-did-mount
and save the response data in the state.
:reagent-render
lifecycle methodAs mentioned above, :reagent-render
is the only required React lifecycle method. This is because it’s used to render HTML.
Just like in Reagent’s Form-1 and Form-2 components, :reagent-render
returns Hiccup and updates when there is a change to either its argument or a subscribed deref
.
In the following example, the component example-component
takes in an argument and renders it among the H1
text (concatenates the argument to the string):
(defn example-component [some-arg] (r/create-class {:reagent-render (fn [arg] [:div [:h1 (str "The argument is: " some-arg)] ] ) }) )
Then when it’s time to use the component, you pass in the argument. In this case, we’re passing the string “example”:
(example-component "example") ;; Output: [:div [:h1 "The argument is: example"]]
Going back to the form example, you can use this lifecycle method to instantiate multiple forms and pass different styling properties to each of them, thereby creating forms with different designs.
:component-did-update
lifecycle method:component-did-update
is called immediately after a component is updated. It is invoked after both the props and state of a component have been updated.
This lifecycle method allows you to perform any necessary post-update operations, such as updating the DOM or making an API call. It’s especially useful if you need to perform any operations when a component’s props or state changes.
In the following example, when the component is rendered, it triggers component-did-update
, which increments the count, and the cycle continues:
(defn SomeComponent [props] (let [state (reagent/atom {:count 0})] (fn [] [:div [:h1 "Count: " @state] [:button {:on-click #(swap! state update :count inc)} "Increment" ] ] ) (reagent/create-class {:component-did-update (fn [] (when (= 0 (:count @state)) (swap! state update :count inc))) } ) ))
In a controlled form, you can use component-did-update
to update the data whenever the form is re-rendered.
:component-will-unmount
lifecycle methodThe :component-will-unmount
lifecycle method is invoked immediately before a component is removed from the DOM tree and destroyed. Typically, you’d use this method to delete objects and remove network timers, which prevents memory leaks and ensures that the component is properly disposed of.
In the following example, we reset the unmount
variable to true
within the :component-will-unmount
lifecycle method:
(defn my-component [data] (let [unmount (r/atom false)] (fn [] [:div [:h3 "My Component Title"] [:div "Component Content"] (when @unmount [:component-will-unmount (fn [] ;; Do something when the component unmounts (reset! unmount true))]])))
component-will-unmount
is the final React lifecycle method that is applicable to re-frame. In the form example we discussed above, this method would be the ideal place to delete all variables and data used by the form.
In this article, we learned how to set up a ClojureScript application with Leiningen and scaffold a React template with re-frame. We built a form by converting HTML to Hiccup syntax, and, finally explored the different React lifecycle methods that are made available to us in the re-frame library built on ClojureScript.
If you need more information on ClojureScript, you can read our article on Getting started with ClojureScript as a JavaScript developer.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]