Hi, I'm Martin.Say Hi on Twitter: @martinklepsch

May 2016Props, Children & Component Lifecycle in Reagent

Every now and then I come across the situation that I need to compare previous and next props passed to a Reagent component. Every time again I fail to find some docs and figure it out by trial and error.

Props vs. Children

In React everything passed to a component is called props. Children passed to components are passed as props.children. In Reagent things are a bit different and Reagent’s hiccup syntax doesn’t explicitly separate the two:

;; configuration and one child
[popup {:style :alert} [delete-confirmation]]
;; two children
[popup [alert-icon] [delete-confirmation]]
<Popup style="alert"><DeleteConfirmation></Popup>

In React it is well-defined where you can access the style parameter (props.style) and how you can access the passed children (props.children).

In Reagent things are a bit different: you have a function definition which takes a number of arguments which you can just refer to in the same way you can refer to any other function parameter. This makes thinking in functions a lot easier but also overshadows some of the underlying React behaviour.

In a lifecycle handler like :component-did-update accessing component arguments via the symbol they’ve been given in the functions argument vector doesn’t work:

The moment you define components that are not simple render functions (remember those Form-2 and Form-3 components?) all updates will pass their arguments to the components render function.

The moment you render a component that has been created via reagent.core/create-class all updates will pass their arguments to the :reagent-render function, potentially triggering a re-render. The function that returned the result of create-class is only ever called once at the time of mounting the component — your top-level defn returns a component instead of being a render function itself. This is also why you need to repeat the arguments in the :reagent-render arguments.

Props in Lifecycle Handlers

Now how do we get access to these props in a lifecycle handler? The quick answer is, we use reagent.core/props — obvious, huh?

One peculiarity about the props function is that it expects the props data to be the first argument to your function. Also it has to be a map (if it’s not props returns nil).

If the first argument to your component is not a map all arguments are interpreted as children and can be retrieved via reagent.core/children.

So now we have the props for the current render, how do we access the previous ones? All previously passed arguments are passed to the lifecycle handler. Not as you might think though.

If you have a component that has a signature like this:

(defn my-comp [my-props more] …)

You can access it’s previously passed arguments like this:

:component-did-update (fn [comp [_ prev-props prev-more]] …))

comp is a reference to the current component. The second argument which is being destructured here contains what we’re looking for. As far as I understood the first item is the components constructor. The rest are the previously rendered inputs (again in React they’re all props, in Reagent they’re props and children).

As you can see you can inspect all previous arguments to a component. The way you access them differs from the default React lifecycle method signatures so hopefully this post helps to clear up some confusion about this stuff. :)


November 2015Om/Next Reading List

A small dump of things I read to learn more about Om/Next. Most of these I stumbled upon while lurking in #om on the Clojurians Slack.

Thinking in Relay

This is Facebook's high level overview for Relay. It explains the reasoning for colocating queries and how data masking allows developers to write components that are not coupled to their location in the UI tree.

Om/Next Quick Start

This is the official Om/Next quick start tutorial. It guides you through building a basic application with Om/Next and introduces the basic API for queries and mutations. After reading this you should have a rough idea what's being talked about in the next two reads.

Om/Next The Reconciler

Kovas Boguta who previously gave an Om/Next workshop with David Nolen wrote this introduction to the Om/Next reconciler. It covers the architectural role of the reconciler managing application state and communicating it to components. The reconciler also acts as an indexer of all components and, using their queries to build a depdency graph, knows when to update which components.

Om/Next Overview

Written by Tony Kay this overview covers many practical aspects of writing queries and mutations. Before it goes into the nitty gritty details howvever there is another short Problem → Solution section that nicely describes the concepts in Relay and Om/Next in prose.

Now put all those links into Instapaper/Pocket & enjoy reading!


August 2015Parameterizing ClojureScript Builds

Just like with most server side software we often want to make minor changes to the behaviour of the code depending on the environment it's run in. This post highlights language and compiler features of ClojureScript making parameterized builds easy peasy.

On servers environment variables are a go-to solution to set things like a database URI. In ClojureScript we don't have access to those. You can work around that with macros and emit code based on environment variables but this requires additional code and separate tools.

With ClojureScript 1.7.48 (Update: There was a bug in 1.7.48 goog-define. Use 1.7.107 instead.) a new macro goog-define has been added which allows build customization at compile time using plain compiler options. Let's walk through an example:

(ns your.app)
(goog-define api-uri "http://your.api.com")

goog-define emits code that looks something like this:

/** @define {string} */
goog.define("your.app.api_uri","http://your.api.com");

The goog.define function from Closure's standard library plus the JSDoc @define annotation tell the Closure compiler that your.app.api_uri is a constant that can be overridden at compile time. To do so you just need to pass the appropriate :closure-defines compiler option:

:closure-defines {'your.app/api-uri "http://your-dev.api.com"}

Note: When using Leinigen quoting is implicit so there is no quote necessary before the symbol.

Under the hood

When compiling with :advanced optimizations the Closure compiler will automatically replace all occurrences of your defined constants with their respective values. If this leads to unreachable branches in your code they will be removed as dead code by the Closure compiler. Very handy to elide things like logging!

Without any optimizations (:none) goog.define makes sure the right value is used. There are two global variables it takes into account for that: CLOSURE_UNCOMPILED_DEFINES and CLOSURE_DEFINES. When you override the default value using :closure-defines the ClojureScript compiler prepends CLOSURE_UNCOMPILED_DEFINES with your overridden define to your build causing goog.define to use the value in there instead of the default value you defined in your source files.

For details see the source of goog.define.


July 2015ClojureBridge Berlin

About two weeks ago something awesome happened: the very first ClojureBridge workshop in Berlin. After months of planning things finally got real.

ClojureBridge Berlin in it's entirety.

ClojureBridge aims to increase diversity within the Clojure community by offering free, beginner-friendly Clojure programming workshops for women.

Many of you probably got the "news": there's a lack of diversity in programming communities. Many communities acknowledge this and have created initiatives to fix it. The Ruby community has RailsBridge (and more) and other communties equally do their part in improving our industries diversity situation. Inspired by RailsBridge the Clojure community established ClojureBridge and has organized more than 20 workshops worldwide since.

Why Diversity?

There are endless amounts of research why diversity is desirable but one of the reasons that seems most intuitive to me is that software is, after all, for humans. If we want to make great software for everyone then it can only be made by all of us and not by one priviliged monoculture.

ClojureBridge Berlin

ClojureBridge workshops consist of one evening installing required software (Friday) and a full day of actually learning things (Saturday). Besides some problems with our pizza delivery both days went really well. We had great vegan and vegetarian lunch on Saturday, fun ClojureBridge cupcakes and after the coffee machine broke on Friday people brought lots of coffee making equipment to the event on Saturday. You could say we had a little third wave coffee workshop as well.

We got some sweet cupcakes!

On Saturday we initially had 2-3 coaches that "didn't have a job" and we were afraid they might feel superflous but the need for some additional help quickly arised when some learners got ahead of the rest of their group. In the end we were very happy that we had the flexibility of not having assigned all coaches to groups. (We still had teaching assistants.)

Results

At the end of the event we had a fantastic demo time. A great amount of learners showed their Quil creations, from an Santa Claus to stroboscopic rainbow animations. It was great to see how in the beginning everyone was shy to show their work but as more people did others felt encouraged to do the same.

Takeaways

This was the first time we organized such workshop in Berlin. We were lucky to be a big team of organizers (six people) which allowed us to distribute the work.

The feedback we got during and after the workshop has been very positive. About a third of the attendees have registered interest in joining project groups to keep learning. Obviously the more the better but even ten people is a nice outcome overall.

Thanks

I'd like to take the opportunity to thank all of our coaches: Nils, Sean, Paulus, Jan, Johannes, Ben, Franziska, Luca, Txus, Kofi, Torsten, Tibor, Thomas, Stephan, Oskar, Kai & Matt thank you so much for being part of this. None of it would have happened without you!

Also I'd like to thank my fellow organizers for pushing through the finish line together and for just being an overall awesome bunch. Thanks Bettina, Malwine, Arne, Jelle & Nicola.

Last but not least I'd like to thank the companies that enabled ClojureBridge Berlin: Wunderlist, SoundCloud, GitHub, InnoQ, TicketSolve, Babbel & DaWanda. A special thank you in this regard to Andrei, who has done an exceptional job at hosting the event at Wunderlist!

ClojureBridge Berlin T-Shirts

If you'd like to be informed about upcoming workshops, follow @ClojureBerlin on Twitter. If you don't have Twitter you can also send me an email and I'll make sure you'll be notified :-)


June 2015Managing Local and Project-wide Development Parameters in Leiningen

Little tip. Long headline.

In any project there are often settings that are specific to the context the project is run in (think of an environment parameter) and then there are parameters that are specifc to the developer/workstation they're run on. This is a guide to separate these two things nicely in Leiningen-based Clojure projects.

So you have a project setup that uses environ to determine the context the project is run in (development vs. production).

; in project.clj:
(defproject your-app "0.1.0-SNAPSHOT"
  ; ...
  :profiles {:dev {:env {:environment "development"}}})

Now you also want to use environment variables (or anything else thats supported by environ) to store AWS credentials to access Amazon S3. You don't want to commit these credentials into version control, therefore you can't add them to project.clj. The way to go is to create a file profiles.clj in your project to store workstation specific information. Naively this could look something like this

{:dev {:env {:aws-access-key "abc"
             :aws-secret-key "xyz"
             :s3-bucket "mybucket"}}}

If you run your project with this profiles.clj you will be able to access your AWS credentials. You might also notice that (environ/env :environment) is nil. That wasn't intended.

The problem here is that Leiningen will override keys in profiles defined in project.clj if the same profile has also been defined in profiles.clj. To recursively merge Leiningen profiles combine them like so:

;; in project.clj:
(defproject your-app "0.1.0-SNAPSHOT"
  ;; ...
  :profiles {:dev [:project/dev :local/dev]
             :project/dev {:env {:environment "development"}}})

;; in profiles.clj
{:local/dev {:env {:secret-key "xyz"}}}

Now both, :envrionment and :secret-key should be defined when you retrieve them using environ.

This is largely inspired by James Reeves' duct Leiningen template.

12345678