Learn how to build dynamic user interfaces using Reagent, a ClojureScript interface to React. Explore component creation, state management, and lifecycle events.
In this section, we will explore how to build a dynamic and responsive user interface using Reagent, a minimalistic ClojureScript interface to React. Reagent leverages the power of React while allowing developers to write components in ClojureScript using a syntax called hiccup. We’ll cover creating components, managing state with Reagent atoms, and handling component lifecycle events. This guide is designed for experienced Java developers transitioning to Clojure, so we’ll draw parallels between Java and Clojure concepts where appropriate.
Reagent is a ClojureScript library that provides a simple and efficient way to build React components. It allows developers to write components using ClojureScript, offering a more concise and expressive syntax compared to JavaScript. Reagent’s use of hiccup syntax makes it easy to define HTML-like structures directly in ClojureScript.
In Reagent, components are defined using hiccup syntax, which is a Clojure data structure that represents HTML. This syntax allows you to write HTML-like code directly in ClojureScript.
Let’s start by creating a simple Reagent component that displays a greeting message.
(ns my-app.core
(:require [reagent.core :as r]))
(defn greeting []
[:div
[:h1 "Hello, Clojure!"]])
(defn mount-root []
(r/render [greeting]
(.getElementById js/document "app")))
(defn init []
(mount-root))
Explanation:
my-app.core
and require reagent.core
as r
.greeting
function returns a hiccup vector representing a div
with an h1
element.mount-root
function renders the greeting
component into the DOM element with the ID “app”.init
function is called to start the application.Modify the greeting
component to display a personalized message. Try adding more HTML elements, such as a paragraph or a list.
State management is a crucial aspect of building interactive user interfaces. Reagent provides a simple way to manage state using atoms, which are mutable references to immutable values.
Let’s create a component that displays a counter and allows the user to increment it.
(defn counter []
(let [count (r/atom 0)]
(fn []
[:div
[:p "Current count: " @count]
[:button {:on-click #(swap! count inc)} "Increment"]])))
(defn mount-root []
(r/render [counter]
(.getElementById js/document "app")))
Explanation:
count
initialized to 0
.@count
to dereference the atom and get its current value.swap!
function is used to update the atom’s value when the button is clicked.Experiment with the counter component by adding a decrement button. Use the reset!
function to add a button that resets the counter to zero.
Reagent components can respond to lifecycle events, similar to React components. This allows you to perform actions at specific points in a component’s lifecycle.
Let’s create a component that logs messages to the console when it mounts and unmounts.
(defn lifecycle-demo []
(r/create-class
{:component-did-mount
(fn [] (js/console.log "Component mounted"))
:component-will-unmount
(fn [] (js/console.log "Component will unmount"))
:reagent-render
(fn []
[:div "Check the console for lifecycle messages"])}))
(defn mount-root []
(r/render [lifecycle-demo]
(.getElementById js/document "app")))
Explanation:
:component-did-mount
and :component-will-unmount
to log messages when the component mounts and unmounts.:reagent-render
function defines the component’s UI.Add more lifecycle methods, such as :component-did-update
, to log messages when the component updates. Experiment with different lifecycle events to see how they work.
In Java, building a user interface typically involves using libraries like JavaFX or Swing. These libraries require a more verbose syntax and imperative approach compared to Reagent’s functional style.
Here’s a simple JavaFX example that creates a window with a button:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
@Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(event -> System.out.println("Hello World"));
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Comparison:
Reagent allows you to compose components by nesting them within each other. This promotes code reuse and modularity.
(defn button [label on-click]
[:button {:on-click on-click} label])
(defn app []
[:div
[button "Click me" #(js/alert "Button clicked!")]])
Explanation:
button
component is reusable and can be used with different labels and click handlers.app
component composes the button
component within a div
.You can conditionally render components based on state or props.
(defn conditional-render [show?]
(fn []
[:div
(when show?
[:p "This text is conditionally rendered"])]))
Explanation:
when
function is used to conditionally render a paragraph based on the show?
argument.Now that we’ve explored how to build user interfaces with Reagent, let’s apply these concepts to create dynamic and interactive web applications.