From 141c28678dc3e872bb67221056d3561d197909c5 Mon Sep 17 00:00:00 2001 From: Jakub Holy Date: Sat, 19 Feb 2022 22:58:36 +0100 Subject: [PATCH 1/4] Split MFT: intro --- .../pages/index.adoc | 2 +- .../pages/index.v2.intro.adoc | 90 +++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc diff --git a/modules/tutorial-minimalist-fulcro/pages/index.adoc b/modules/tutorial-minimalist-fulcro/pages/index.adoc index 66e6d75..63818b6 100644 --- a/modules/tutorial-minimalist-fulcro/pages/index.adoc +++ b/modules/tutorial-minimalist-fulcro/pages/index.adoc @@ -2,7 +2,7 @@ :toc: :toc-placement!: :toclevels: 2 -:description: a minimalistic introduction to Fulcro that focuses on HOW and (almost) not WHY. The goal is provide you with the basic building blocks so that you can create full-stack web applications. +:description: A minimalistic introduction to Fulcro that focuses on HOW and (almost) not WHY. The goal is provide you with the basic building blocks so that you can create full-stack web applications. :url-book: https://book.fulcrologic.com/ :url-eql: https://edn-query-language.org/eql/1.0.0 diff --git a/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc b/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc new file mode 100644 index 0000000..e43e401 --- /dev/null +++ b/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc @@ -0,0 +1,90 @@ +# Minimalist Fulcro Tutorial Series: Introduction +:toc: +:toc-placement!: +:toclevels: 2 +:description: A minimalistic introduction to Fulcro that focuses on HOW and (almost) not WHY, centered and split around the concerns you want to address. The goal is to provide you with only those basic building blocks that you need for your web application. + +:url-book: https://book.fulcrologic.com/ +:url-eql: https://edn-query-language.org/eql/1.0.0 +:url-pathom: https://blog.wsscode.com/pathom/v2/pathom/2.2.0/ +:url-pathom-resolvers: https://blog.wsscode.com/pathom/v2/pathom/2.2.0/connect/resolvers.html +:url-divergence: https://blog.jakubholy.net/2020/fulcro-divergent-ui-data/ + +Author: https://holyjak.cz/[Jakub Holý] & contributors + +Welcome to learning Fulcro, the malleable web framework for sustainable development of real-world, full-stack web applications! (You will soon understand that.) + +This is a _minimalist_ tutorial that aims to provide you with only what you absolutely need to get building your application. It is split by concerns so that you can easily find and pick just the parts relevant to you. Even though Fulcro is distributed as a single library for reasonsfootnote:[The overhead of versioning, releasing, and consuming many small libraries turned out not to be worth it. And thanks to Google Closure's Dead Code Elimination, it does not matter that there is code you do not use.], it is effectively a small core and a set of optional "modules" that address various extra concerns. The content of this tutorial is thus as described in the following section. + +TIP: You might want to have a look at my self-paced https://github.com/holyjak/fulcro-intro-wshop[workshop Introduction to creating webapps with Fulcro] to get an overview of what Fulcro offers for full-stack web applications and to get familiar with its concepts and the amazing tooling before deep-diving into learning the details. + +## Contents of this tutorial + +Fulcro has two core parts that you almost always use together: view rendering and state management. But they actually independent. You can use Fulcro with a different view technology, https://www.youtube.com/watch?v=ng-wxe0PBEg&t=1392s[e.g. with re-frame], even a non-React one https://github.com/phronmophobic/membrane-fulcro[such as Membrane/Skia] for desktop and terminal UIs. And you can use only Fulcro's view rendering without any state management (though you would be better served by a view-only library). + +. Introduction (this page) +. Core: View Rendering (TBD) +. Core: State Management - Store and read data (TBD) +. Core: State Management - Mutate data (TBD) +. Loading data from a backend (TBD) +. Implementing a backend providing data (TBD) +. Next steps (TBD) + +## Why Fulcro? + +People love Fulcro because of the short-term and long-term *productivity* and flexibility that it affords. On the one hand Fulcro allows you to quickly create a full stack web application to display and manage data and on the other hand you don't need to be afraid of ever outgrowing the framework. As your domain becomes more complex, as new needs emerge and old needs change - Fulcro has you covered. That is because it has been designed for *sustainable development* of *real-world* applications. In other words, its raison d'être is for you not to drown in complexity as time goes, your code base grows, and new needs appear. Thus it works hard to minimize accidental complexity and boilerplate code while remaining flexible. The very core of Fulcro are not some features but its practical philosophy. As the author Tony Kay puts it: + +> [..] to me Fulcro is about a pattern of data management, reasoning, and to some extent scalable code bases +> (though that is very much tied to the people doing it). + +What does it mean? In Fulcro, data is king. The UI is essentially just a pure function of data. When something does not work, in 90% cases you don't need to mess with the UI, you just need to look at the data. And the data is structured in such a way that it is always trivial to find the piece of data that you need or want to change. It is also hot on "local reasoning" so that you can see and understand everything relevant to the piece of UI you work with right there, where it is defined (or at most the relevant behavior is one click away). + +Non-trivial web applications typically need to address a number of concerns from a various stakeholders such as the users and the developers. These typically include but are not limited to: + +* Displaying a piece of data in the UI +* Getting the said piece of data from a remote data source +* Finding easily all the behavior and structure relevant to the current piece of UI / functionality +* Informing the user while an operation is ongoing or if it failed +* Providing the feeling of responsiveness via optimistic UI updates (i.e. behave as if the remote operation succeeded and recover later if it fails) +* Caching remote data +* Troubleshooting the state of the application and its past transitions + +Your needs might be simple at first and you may be tempted to pick a simple, focused library, e.g. one that only does view rendering, such as the excellent Reagent. Eventually it gets more complicated and you will need better defined state management and add another library, perhaps re-frame. As the amount of interaction with the backend grows, you will spend more and more effort juggling and placing data, handling progress indicators, and dealing with errors. Maybe you add few other libraries to ease some of the pain points. They were mostly not designed for each other but it sort of works, with some plumbing. But your code base grows, navigating it becomes harder, finding all the pieces of code relevant to a particular functionality becomes nontrivial, onboarding new developers is hard. + +You could instead pick Fulcro. Your needs are simple so you only need to learn and adopt its very core. As the application grows and needs expand, you adopt more of Fulcro to satisfy them. All works perfectly together. No extra boilerplate. You truly start to appreciate that you can reason locally and navigate easily through control/command-click. Nirvana. + +### The way Fulcro is + +NOTE: We discuss here Fulcro as a full-stack solution. But as mentioned above, you do not need to adopt all those parts (until you do need them). + +This focus on enabling sustainable development speed over the long-term led to the following decisions: + +* UI = f(state) - React came with the idea that the UI is just a pure function of data but Fulcro really means it. When you have a problem, look at the data, not the UI. 99% of the time it is there. +* UI components declare their data needs ("query") - because nobody else knows or should care about what data the component needs. And these queries are composable so that we can fetch the data needed by the whole UI (sub)tree at once. +* Graph API: The UI is a a tree (i.e. a graph) of components and therefore the composed query is also a tree. The server can understand and fulfill such a graph query with a tree of data - exactly the data the UI needs. Not the mess of N separate REST endpoints that you need to query individually and combine and prune the data on the frontend. (Reportedly, a perfectly designed REST APIs do not suffer from this problem. But they are rarer than unicorns.) +* Web applications are inherently full-stack and thus a framework should provide an integrated solution for fetching data from the server - including the ability to track its status - and for triggering actions ("mutations") with potentially both local and remote constituents. We should not pretend that this is not our problem, as many React frameworks do. +* Normalized client-side DB: Even though the UI needs a tree of data and the server returns just that, we want the data cached in a https://en.wikipedia.org/wiki/Database_normalization[normalized] cache - which we call client DB - on the frontend. For decades, data normalization has been the established best practice for data access in databases, and for good reasons. In particular, it prevents a whole class of issues with out-of-sync data. When we mutate a particular piece of data, we want the new value reflected everywhere where it is used, without having to manually go through all those places. And such a normalized database also makes it trivial to find just the piece of data you want to change (all you need is the entity's name, its ID value, and the property name). + +Fulcro is also quite well designed. It is based on a small set of orthogonal building blocks and it doesn't hide anything from you - you can always go a level deeper into its internals to achieve what you need. Its flexibility and customizability is surprising - all of the critical features from network interaction to rendering optimization are easily customizable. It is also very careful about providing only the tools that are generally applicable and avoiding "features" that might help some people but stand in the way of others. But thanks to the aforementioned flexibility, you can implement what you need for your unique use case yourself. Developers rightly see frameworks as tools that provide an initial boost but eventually become straitjackets that limit them, when the application's unique needs do not align perfectly with what the authors foresaw. Fulcro suffers from this far less then any other framework, as long as you are aligned with its overall philosophy, because it allows you to customize so much. (But of course the more you want to change the more work it will require.) + +What do users say (redacted slightly for correctness): + +> I'm positively surprised, almost shocked... Not only because it seems to be very carefully crafted and designed, but also because it manifests several concepts, practices and intuitions that I've been using and gathering, and then goes way beyond that by refining and composing them into a whole, adaptable system. I so far feel blessed and lucky, because Fulcro validates my half baked tools, ideas and practices, but also because it seems to be a framework that I can build on with confidence. +> +> -- Denis Baudinot, a freelance full-stack developer, December 2020 + +**** +To learn more about the reasoning behind Fulcro, listen to the ClojureScript Podcast https://podcasts.apple.com/us/podcast/s4-e6-fulcro-with-tony-kay-part-1/id1461500416?i=1000479361034[S4 E6 Fulcro with Tony Kay (Part 1)] (2020), which explores the origins of and key motivation for Fulcro, and read the {url-book}#_getting_started[Ch. 4. Getting Started] of the Fulcro Developers Guide, which demonstrates how various problems are made easier thanks to the way Fulcro is. To learn to use Fulcro, read on :-). +**** + +### Fulcro or re-frame? + +If you know or have heard about the popular web framework http://day8.github.io/re-frame/[re-frame], you might wonder whether to pick that or Fulcro. Tony Kay has an answer (elided): + +> I would claim that Fulcro's central core (for UI) is actually *simpler* (not necessarily easier, at first) than Reframe. There is a normalized db, a query, and a UI tree. There are no side-effects mixed into the render, etc. Every modification goes through a single concept: the mutation. The fact that this handles full-stack equally as well as client-only means there is even less to deal with *conceptually*. BUT, you do have to learn the nuances (query/ident/initial state are the three big concepts). Fulcro is partially hard because I've provided so many parts. But 80% of them are completely optional, [..] Fulcro is opinionated where it is useful, and completely configurable where it should be flexible. +> +> [..] Another fair point: If it's a small project with little or no I/O, I'd agree with Reframe. It's very tractable and easy for that kind of project. That said, I've had more than one consulting client come to me for help porting from Reframe to Fulcro when their project got bigger. +> +> -- https://www.reddit.com/r/Clojure/comments/kibrfs/comment/ggvih7x/[Tony Kay on Reddit, 2021] + +// Other pros: Docs - in code + Guide \ No newline at end of file From 7327a5375eef44131c142ec4f717b73650207c8a Mon Sep 17 00:00:00 2001 From: Jakub Holy Date: Wed, 2 Mar 2022 11:56:44 +0100 Subject: [PATCH 2/4] MFTs: view render --- .../pages/1-view-rendering.adoc | 202 ++++++++++++++++++ .../pages/index.v2.intro.adoc | 2 + 2 files changed, 204 insertions(+) create mode 100644 modules/tutorial-minimalist-fulcro/pages/1-view-rendering.adoc diff --git a/modules/tutorial-minimalist-fulcro/pages/1-view-rendering.adoc b/modules/tutorial-minimalist-fulcro/pages/1-view-rendering.adoc new file mode 100644 index 0000000..c18c898 --- /dev/null +++ b/modules/tutorial-minimalist-fulcro/pages/1-view-rendering.adoc @@ -0,0 +1,202 @@ +# Minimalist Fulcro Tutorial Series: Rendering UIs +:toc: +:toc-placement!: +:toclevels: 2 +:description: *TODO* + +This is part 1 of the link:index.html[Minimalist Fulcro Tutorial Series] (and the first of the two core parts), where we learn how to render data in a web page. + +NOTE: It doesn't make much sense to use Fulcro only as a view technology. Reagent or Helix are better fits for this simple case. Fulcro's true power shines when it is used for state management, especially in combination with server interactions. (And you can even use it with a different view technology, such as Reagent.) + +toc::[] + +## The Problem + +You want to render business data in a web page for the user to view and interact with. And you want the view to update when the data changes, efficiently. You most certainly don't want the UI and the data to become inconsistent. + +## The Solution + +The genially simple idea of React - that Fulcro builds uponfootnote:[Fulcro is most commonly used with React but have also been used with other view technologies such as text UIs and graphical toolkits] - is: + +``` +View = function(Data) +``` + +in words, the view is a "pure" function of the data - we pass all of the data to our render function and get back the view, instead of doing tons of small updates to parts of the view as the data changes. Much simpler. + +The view is naturally a _tree_ of _components_, just as is the case with HTML: imagine a page containing tabs, a tab containing a list of items, each item containing a link... . The data we pass to the view mirrors its shape, i.e. it is also a tree. In React we pass the data tree - called _props_, short for properties - to the root component (the page in our example), which uses whatever it needs (e.g. the tab labels to render a tab switcher) and then passes relevant sub-trees to its child components (e.g. the data of the active tab to the tab component). + +Fulcro is not very different from other ClojureScript React wrappers in this regard. Let's see what it looks like. + +### Creating a view with Fulcro + +Here we are going to learn how to create a view - i.e. an HTML fragment - with Fulcro. + +#### The building blocks + +##### Creating HTML elements + +.Example +```clojure +(dom/section + :#main.two-columns.highlight + {:style {:border "1px"}} + "Hello " (dom/strong "there") "!") +``` + +.Example rendered +```html +
+ Hello there!) +
+``` + +.General structure +```clojure +(ns x (:require [com.fulcrologic.fulcro.dom :as dom])) + +(dom/ ; `` is any HTML5 element such as div, h1, or ul + <[optional] keyword encoding classes and an element ID> ; <1> + <[optional] map of the tag's attributes (or React props)> ; <2> + <[optional] children>) ; <3> +``` +<1> A shorthand for declaring CSS classes and ID: add as many `.` as you want and optionally a single `#`. Equivalent to the attributes map `{:classes [ ...], :id }`. +<2> A Clojure map of the element's attributes/props. In addition to what React supports, you can specify `:classes` as a vector of class names, which can contain `nil` - those will be removed. It is merged with any classes specified in the keyword shorthand form. +<3> Zero or more children + +##### Defining a Fulcro component + +The view is composed of a tree of _components_ that take props and render DOM. We define a component like this: + +.Example +```clojure +(defsc Root [this props] + {} + (dom/p "Hello " (:name props) "!")) +``` + +.General structure +```clojure +(ns x (:require [com.fulcrologic.fulcro.components :as comp :refer [defsc]])) + +(defsc ; <1> + [] ; <2> + {} ; <3> + ) ; <4> +``` +<1> `defsc` stands for **Def**ine **S**tateful **C**omponent and it is a macro that produces a JS class extending `react/Component` (unless you opt for using hook-based function components by setting the option `:use-hooks? true`) +<2> The arguments are `this` and the component's `props` and are available in the body. The props is a _Clojure_ map, not JS, thanks to Fulcro +<3> A map of options that typically includes `:ident`, `:query`, `:initial-state`, routing-related options, and any custom options you add. We will not use it in this tutorial +<4> The body will become the render function of the React component, i.e. this is what will produce the DOM + +##### Child components + +Having a single huge component is hard to maintain. We can break parts of the view into separate components and include those in a parent component: + +```clojure +(defsc Child [_ props] {} (:name props)) +(def ui-child (comp/factory Child)) ; <1> +; or: (def ui-child (comp/factory Child {:keyfn :name})) ; <2> + +(defsc Parent [_ props] + {} + (dom/div "I, " (:name props) " am the father of:" + (ui-child (-> props :kids first)))) +;; Assuming that the parent props are e.g.: +;; {:name "Darth Vader" :kids [{:name "Luke"}]} +``` +<1> We need to turn the `Child` JS class into a function that returns a React element given props +<2> `comp/factory` also can take a map of options, the key one being `keyfn`, which should be a function of props that returns a unique identifier for the child. https://reactjs.org/link/warning-keys[React needs a key] when children are rendered in a list (e.g. via `mapv`), otherwise it complains that "`Each child in a list should have a unique "key" prop.`" + +*About factories*: `comp/factory` returns a function turning props into an actual element. Some frameworks and JSX hide this transformation, while Fulcro keeps it visible. And it is a good thing because it makes it easier to customize the elements, f.ex. by setting the `keyfn` or to make it simpler to pass in extra props (such as callbacks) via `comp/computed-factory`. (Which is beyond the scope of this tutorial) + +##### Mounting and rendering the view + +Having defined a view via a component, we need to supply it its props and render it to somewhere in the DOM. We will look at two ways of doing it. + +First we will look at rendering a Fulcro component when using it just for view management (though, as discussed at the beginning, there is little sense in that): + +.Rendering a Fulcro component via raw React interop +```clojure +(ns x (:require ["react-dom" :as rdom] + [com.fulcrologic.fulcro.application :as app])) + +;; Assuming the html page has a block element with id=app, we do: +(rdom/render (comp/with-parent-context (app/fulcro-app) ; <1> + ((comp/factory Root) props)) ; <2> + (js/document.getElementById "app")) ; <3> +``` +<1> Fulcro components assume they are used in the context of a Fulcro app so we need to pass it in even though we don't really use it here +<2> As explained in <>, we need to turn the Root class into an actual React element, passing in the props +<3> Finally we need to put the rendered DOM somewhere into the HTML page, here into the element with the id _app_ + +If we also use Fulcro for state management, maintaining the app state inside the fulcro-app instance, then we can use its standard way of rendering: + +.Rendering a Fulcro component the standard Fulcro way +```clojure +(defonce app (app/fulcro-app {:initial-db props})) ; <1> +(app/mount! app Root "app" {:initialize-state? false}) ; <2> +``` +<1> Initialize the Fulcro app and set the props as the initial app state (normally you would `df/load!` the data from a server or use `merge!` or `merge-component!` - we will discuss these in the next tutorial) +<2> Turn Root into an element and render it inside the element with the id _app_; do not initialize state since we have already set it above + +###### Updating the view on a data change + +To update the UI when data changes: + +* If you use the `rdom/render` approach then simply re-run the call to render +* If you use the standard `app/mount!` then Fulcro will automatically re-render the UI if the data changes using its standard `transact!` mechanism, which we will discuss in the next tutorial + +#### A complete example + +.The HTML fragment we want to get +```html +
+

Hello Sokrates !

+

Below are some tabs

+
  • Tab 1
+
+``` + +.The Fulcro view definition +```clojure +(def props + {:username "Sokrates" + :tabs [{:label "Tab 1"}]}) + +(defsc Tab [this {:keys [label]}] + {} + (dom/li label)) + +(def ui-tab (comp/factory Tab {:keyfn :label})) + +(defsc Root [this props] ; <1> + {} ; <2> + (dom/div ; <3> + (dom/h1 :#hdr1.pagetitle "Hello" (:username props) "!") ; <4> + (dom/p {:style {:border "1px black"}} "Below are some tabs") + (dom/ul + (mapv ui-tab (:tabs props))))) + +(defonce app (app/fulcro-app {:initial-db props})) +(app/mount! app Root "app" {:initialize-state? false}) +``` + +## Summary + +TBD + +## TODO + +* Computed props ?! +* React interop (for including JS libs)? +* React lifecycle methods +* Local state ??? (but class vs hooks) +* props: +** While React props must be a JavaScript map with string keys, Fulcro props - both for `defsc` components, `dom/` components, and vanilla JS components link:++{url-book}#_factory_functions_for_js_react_components++[wrapped with `interop/react-factory`] - can be and typically are a _Clojure_ map (possibly containing nested Clojure data structures) with (typically qualified) keyword keys. (Fulcro actually stores its props under "fulcro$value" in the React JS map, but that is transparent to you.) +** You can use lazy sequences of children (produced by map etc.). +* body +** Returning multiple elements from the body +* Even handlers such as `:onClick`? \ No newline at end of file diff --git a/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc b/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc index e43e401..83e3056 100644 --- a/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc +++ b/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc @@ -18,6 +18,8 @@ This is a _minimalist_ tutorial that aims to provide you with only what you abso TIP: You might want to have a look at my self-paced https://github.com/holyjak/fulcro-intro-wshop[workshop Introduction to creating webapps with Fulcro] to get an overview of what Fulcro offers for full-stack web applications and to get familiar with its concepts and the amazing tooling before deep-diving into learning the details. +toc::[] + ## Contents of this tutorial Fulcro has two core parts that you almost always use together: view rendering and state management. But they actually independent. You can use Fulcro with a different view technology, https://www.youtube.com/watch?v=ng-wxe0PBEg&t=1392s[e.g. with re-frame], even a non-React one https://github.com/phronmophobic/membrane-fulcro[such as Membrane/Skia] for desktop and terminal UIs. And you can use only Fulcro's view rendering without any state management (though you would be better served by a view-only library). From e5230f180f167fd9372209dac2fb518f42cf7db9 Mon Sep 17 00:00:00 2001 From: Jakub Holy Date: Wed, 2 Mar 2022 21:08:41 +0100 Subject: [PATCH 3/4] link to part 1 --- modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc b/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc index 83e3056..0834c62 100644 --- a/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc +++ b/modules/tutorial-minimalist-fulcro/pages/index.v2.intro.adoc @@ -25,7 +25,7 @@ toc::[] Fulcro has two core parts that you almost always use together: view rendering and state management. But they actually independent. You can use Fulcro with a different view technology, https://www.youtube.com/watch?v=ng-wxe0PBEg&t=1392s[e.g. with re-frame], even a non-React one https://github.com/phronmophobic/membrane-fulcro[such as Membrane/Skia] for desktop and terminal UIs. And you can use only Fulcro's view rendering without any state management (though you would be better served by a view-only library). . Introduction (this page) -. Core: View Rendering (TBD) +. xref:1-view-rendering.adoc[Core: View Rendering] . Core: State Management - Store and read data (TBD) . Core: State Management - Mutate data (TBD) . Loading data from a backend (TBD) From c7c5d647b2b8737032222f0a4be58f9d78040e1f Mon Sep 17 00:00:00 2001 From: Jakub Holy Date: Wed, 2 Mar 2022 21:45:10 +0100 Subject: [PATCH 4/4] comment on adv of cljs wrappers --- .../tutorial-minimalist-fulcro/pages/1-view-rendering.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/tutorial-minimalist-fulcro/pages/1-view-rendering.adoc b/modules/tutorial-minimalist-fulcro/pages/1-view-rendering.adoc index c18c898..cb0eb62 100644 --- a/modules/tutorial-minimalist-fulcro/pages/1-view-rendering.adoc +++ b/modules/tutorial-minimalist-fulcro/pages/1-view-rendering.adoc @@ -26,7 +26,9 @@ in words, the view is a "pure" function of the data - we pass all of the data to The view is naturally a _tree_ of _components_, just as is the case with HTML: imagine a page containing tabs, a tab containing a list of items, each item containing a link... . The data we pass to the view mirrors its shape, i.e. it is also a tree. In React we pass the data tree - called _props_, short for properties - to the root component (the page in our example), which uses whatever it needs (e.g. the tab labels to render a tab switcher) and then passes relevant sub-trees to its child components (e.g. the data of the active tab to the tab component). -Fulcro is not very different from other ClojureScript React wrappers in this regard. Let's see what it looks like. +When some of the props change, React is smart and does not re-render the components whose props have not changed. This is especially efficient in ClojureScript thanks to the use of immutable data. + +ClojureScript React wrappers such as Fulcro provide you with the efficiency of immutable data and make it convenient to use React from a superior language with unparalleled live-reload and powerful REPL. Fulcro is not very different from other ClojureScript React wrappers in this regard. Let's see what it looks like. ### Creating a view with Fulcro