Selecting JSX elements
(jsx)
selects JSX elements by their name, attributes and children. It has the following signature:
(jsx [name?] [attrs?] [children?])
Selecting an element by its name
JSX elements are identified by their name, which is what you find in the opening tag. An element name is most commonly an identifier, like <Link />
, but it can also be a namespaced identifier, like <ReactRouter:Route />
, or a member expression, like <Router.Route />
.
The first argument to (jsx)
selects the element name. Elements named by an identifier or a namespaced identifier are selected using (id)
:
(jsx X) ; <X />
(jsx NS:X) ; <NS:X />
Elements that are used through a member property are selected using (mem)
:
(jsx (mem B A)) ; <A.B />
You can use a different selector that fits, like (import)
:
(jsx (import @material-ui/core Link)) ; <Link />
To select a JSX fragment, use the <>
identifier:
(jsx <>) ; <></>
Fragment selection behaves similarly to element's only that there are no attributes to select.
Selecting an element by its attributes
JSX element attributes are similar to Object literal attributes. In fact, the default JSX preprocessor translates them to Object literals under the hood. To select them, we can provide (attr)
as the second argument to (jsx)
:
(attr [name?] [value?]) ; synonymous with (prop) in (obj)
Attribute values can be JSX expressions and not just literals, but again SYNG does not make that distinction in the selector interface: you're still able to select the value in either form by specifying the value argument to (prop)
. For example, to select a <Link />
whose href
attribute has the value of #bookmark
:
(jsx Link (attr href
(str #bookmark))) ; <Link href="#bookmark" />
; <Link href={"#bookmark"} />
When the element name is irrelevant, but the attribute isn't, you can use the placeholder atom to disregard it:
(jsx _ (attr href))
Selecting an element by its children
A JSX element can contain other JSX elements that it calls its children. Children are positional, just like Array literal elements. The third argument to (jsx)
selects the children of an element through the (child)
selector:
(child [pos?] [val?]) ; synonymous with (el) in (arr)
For example, to select a <Link />
that contains a <Text />
element as the first child:
(jsx Link _ (child 1 (jsx Text))) ; <Link><Text>Hello world!</Text></Link>
Or, to select a <Link />
that contains a child that is the result of a call to the function translate
:
(jsx Link _ (child _ (call translate))) ; <Link>{translate('Hello world!')}</Link>
JSX text is treated as a String literal, so we can use the (str)
selector to select it:
(jsx Link _ (child 1 (str "Hello World!"))); <Link>Hello World!</Link>
We've already been doing this, but when the attributes are irrelevant and the children aren't, you can use the placeholder atom to disregard them:
(jsx Link _ (child 1 (jsx Text)))
In fact, you can ignore both name and attribute components altogether:
(jsx _ _ (child 1 (jsx Text)))
Composition
The arguments to (jsx)
for selecting attributes and children both support composition in the form we've seen before. To provide a fairly beefy example:
(jsx (import "components/Link")
(:or (attr href)
(attr onClick))
(:and (child _ (str))
(:not (child _ (call translate)))))
Which will match any <Link />
that has either attribute and text content that is not the result of a call to translate()
.
Recap
The (jsx)
selector behaves like a combination of (obj)
and (arr)
, this is because JSX elements have an object-like map of properties as well as an array-like list of elements. To provide a nicer interface, SYNG aliases the (prop)
and (el)
selectors to names that fit within the JSX lexicon: (attr)
and (child)
.