When running in a non-production environment, the style guide can be accessed at /#styles
, or by hovering at the right edge of the footer.
For ClojureScript code, we follow the Clojure Style Guide with exceptions noted here.
Atom
The Lisp Paredit package formats code correctly.
IntelliJ
The Cursive plugin formats code correctly (after a few configuration changes), but is not free. It requires the following configuration:
-
Correct cursive settings are included in this repo in importable form, in the file
IntelliJ-clojure-style.xml
. Import them fromIntelliJ IDEA -> Preferences -> Editor -> Code Style -> Clojure
: -
You need to tell Cursive how to resolve some custom macros:
We feel the 80-character line length limit in the style guide is more restrictive than necessary. Where feasible, avoid making lines longer than 100 characters.
We do not strictly adhere to the guide's suggestion to keep functions under 10 lines of code. In general, however, shorter functions are preferred.
React component names are camel-cased, starting with a capital letter: [comps/Button]
Methods on components are kebab-cased, and "private" (although this is technically unenforced) methods start with a dash: :-create-dropdown-ref-handler
Native clojure(script) methods and structures are kebab-cased: (common/render-info-box)
Method and function names should always be verbs, and structures should be nouns.
The order for required namespaces is as follows:
[dmohs.react :as react]
- any third-party libraries
- any internal clojure namespaces
- any broadfcui namespaces
Within each section, all lines should be alphabetized. Leave the closing double paren on its own line, to avoid excess line changes in git. The only unused namespace that can be left in a require
is utils
.
Avoid refer
ing to a function from the namespace, except when a namespace contains only one public member.
When requiring a namespace, we generally require it as its full name. Some exceptions to this are components
, which is required as comps
, and monitor.common
as moncommon
. Common sense applies.
A full namespace declaration should look like this:
(ns broadfcui.new-namespace
(:require
[dmohs.react :as react]
[inflections.core :as inflections]
[clojure.string :as string]
[broadfcui.common :as common]
[broadfcui.components :as comps]
[broadfcui.utils :as utils]
))
Every React component that's created has a state and props that have to be tracked in memory by the application. When you're creating something, a def
or defn
is preferred over a defc
.
As a quick rule of thumb, if the thing you're creating doesn't have an internal state that needs to be tracked, and it doesn't need to respond to lifecycle events (i.e. component-did-mount
), it shouldn't be a component.
We avoid using CSS files. Instead, we define styles for components in place, along with their logic, so that all of the attributes of a component are described in one place.
Our reasons for this are outlined in this slide deck.
If you're creating a function or value that's only used in one place, for instance inside of only one method on a component, rather than creating a method on the component, let
it at the top of the function. If that makes things too crowded, consider putting it in a private function in the namespace.
A React component's state is considered private to that component. Do not pass the state
atom to another component.
Avoid this:
(react/defc Foo ...)
(react/defc Bar
{:render
(fn [{:keys [state]}]
[Foo {:parent-state state}])})
Instead, do something like this:
(react/defc Foo ...)
(react/defc Bar
{:render
(fn [{:keys [state]}]
[Foo {:handle-some-action (fn [value] (swap! state ...))}])})
(react/defc Foo
{:render
(fn []
[:div {}
;; ^ This is not a real <div> element. It is a vector that will be
;; turned into a React element by the function that calls `render`
;; on this component.
(react/create-element :div {})])})
;; ^ Likewise, this is not a real <div> either. This creates a
;; React element directly.
In non-React JavaScript, you can do things like:
var myDiv = document.createElement('div', ...);
myDiv.focus();
or
var myDiv = document.createElement('div', ...);
SomeThirdPartyLibraryThatTakesADomNode(myDiv);
In situations where a method operates on a DOM node, React elements may not be substituted. You must use a ref
(see React's documentation) to obtain access to the DOM node once React has rendered it into the browser window.
State updates are not immediate, meaning that state
will not immediately contain a new value after you set it, but instead will have that value after the next re-render. For example:
(swap! state assoc :foo 17)
(get @state :foo) ; <- :foo has yet to be changed to 17 here!
So, instead of immediately reading a value back from state:
(swap! state assoc :foo (bar ...))
(some-func (:foo @state))
use the new value directly:
(let [new-value (bar ...)]
(swap! state assoc :foo new-value)
(some-func new-value))
or wait until after the re-render:
(this :some-state-modifying-method)
(after-update #(some-func (:some-key @state))))
Changing state causes a re-render. If you update state in a lifecycle method, this can lead to a loop:
- state changes in
component-did-update
- state change starts re-render
- re-render calls
component-did-update
- state changes in
component-did-update
- state change starts re-render
- ...
So: some lifecycle methods are automatically called every render. Avoid changing state inside of them.
We adhere to Google's official style guide on JS & CSS, which dictate two-space indentation. We indent 4 spaces for html, because 2 spaces looks weird.
A list of any "gotchas" that have been found in development. These may be browser bugs (or "features"), or react issues.
- A "feature" in browsers is that any
button
inside of aform
will cause the page to submit, even if you don't define anon-click
or link attribute on it. Be careful when using buttons inside of forms because the behavior you define may not be the behavior you get.
- See above
When doing UI development, Chrome's caching gets in the way. We recommending disabling it when devtools is open (via devtools settings):