In the last post, we discussed how to set up a Preact project by importing Preact and HTM libraries in "1999-style JavaScript" to help us understand how to modernize a legacy client codebase and/or introduce modern templating conventions to developers who have never used them before.
This pattern allows us to get a componentized paradigm up and running without any serious overhead. Nobody will ask, "What is npm?" or "Why isn't webpack working?" Using Preact and HTM allows for a perfect simplest case, introducing nothing beyond what's immediately needed, but being able to deploy immediately, if that's the goal.
But we only got so far, with one simplest-case component in a static system.
Let's make things a little more complicated,
- Add a second nested "tier" of components to our setup
- Show how to use
children
within our renders - Explore some best practices for including html (only in our leaves!)
- & best practices for including CSS
WARNING: I'm posting this in reverse chronological order so they appear correctly in the blog, which lists the most recent first.
Components nesting in components
Previously, we talked about how the HTM project uses JavaScript tagged templates to do the work React programmers usually accomplish with JSX, ending with a sort of lament that we've got to use so many ${}
brackets.
The ${}
convention gets especially unruly with Components whose templates contain other Components. You have to surround the name of the component in the opening but not the closing tag, which is really weird at first blush.
Let's look at an example that adds a new ParentComponent
component type that will host a few instances of our MyComponent
component inside of it.
ParentComponent.js
(function () {
window.ParentComponent = function (props) {
return html`<div>
<${MyComponent} displayText="${props.first}" />
<${MyComponent} displayText="${props.second}" />
</div>`;
};
})();
MyComponent.js
No changes to this component from last time.
(function () {
window.MyComponent = function (props) {
return html`<div>
<h3>#${props.displayText}#</h3>
<p>This is some normally-sized text.</p>
</div>`;
};
})();
index.html
<html>
<head>
<title>Hello, Preact World!</title>
<script src="https://unpkg.com/preact@10.11.3/dist/preact.umd.js"></script>
<script src="https://unpkg.com/htm@3.1.1/preact/standalone.umd.js"></script>
<script>
window.render = window.preact.render;
window.html = window.htmPreact.html;
document.addEventListener('DOMContentLoaded', function () {
render(
html`<${ParentComponent} first="first text value" second="second text value" />`,
document.getElementById('root-element')
);
});
</script>
<script src="./components/ParentComponent.js"></script>
<script src="./components/MyComponent.js"></script>
</head>
<body>
<p>This is some pre-Preact text</p>
<div id="root-element"></div>
</body>
</html>
Note that if this was JSX, we wouldn't need to surround the component names with ${}
.
JSX
return <div>
<MyComponent displayText="{props.first}" />
<MyComponent displayText="{props.second}" />
</div>`;
That is nicer. But not valid JavaScript! The parser is going to puke as soon as it hits that first <
in <div
. "What does <
mean?"
HTM
return html`<div>
<${MyComponent} displayText="${props.first}" />
<${MyComponent} displayText="${props.second}" />
</div>`;
Not too bad.
But note that if we wanted to use closing tags, we don't wrap those. Fun, huh? We won't need them until we add children to the components, but this is functionally the same as the HTM section, above:
return html`<div>
<${MyComponent} displayText="${props.first}"></MyComponent>
<${MyComponent} displayText="${props.second}"</MyComponent>
</div>`;
I'd almost... almost... prefer to use to match the opening tags, but don't.</${MyComponent}>
Adding children to components
Let's add a way to pass children to MyComponent
which would require using closing tags.
ParentComponent.js
(function () {
window.ParentComponent = function (props) {
return html`<div>
<${MyComponent} displayText="${props.first}">
<b>hello, world</b>
</MyComponent>
<${MyComponent} displayText="${props.second}" />
</div>`;
};
})();
MyComponent.js
I'm going to add a variable to hold the children if they exist (like in ParentComponent
's modified first use of MyComponent) or to hold some default markup, the same that we used earlier before we had children (haha), if there are no children (like in our second
MyComponent` use).
Notice that I'm using a tagged template processed by html
outside of the return
statement if there are no children for the component. That should be fine.
(function () {
window.MyComponent = function (props) {
var toDisplay = props.children || html`<p>This is some normally-sized text.</p>`;
return html`<div>
<h3>#${props.displayText}#</h3>
${toDisplay}
</div>`;
};
})();
Result:
This is some pre-Preact text
#first text value#
hello, world#second text value#
This is some normally-sized text.
Best practice: Only have html in your leaves
There a best practice in to point out here: You should try to keep html only in the "leaf" components.
That is, think of each component as a node, and let's say that each node can be either 1. a leaf or 2. a branch, and that you can tell which type of node of you have by asking, "Does this component's rendering include another Preact component?" If a component does contain another component, it's a branch with more component nodes below. If it doesn't, and the rendered HTM contains only html, it's a leaf.
Let me propse that, in the best case, only your leaf components should include html.
This helps keep your UI simple, readable, and, until the very last minute, properly abstract.
In our example, we have one ParentComponent
"branch" that contains two MyComponent
leaves, leaves because they only contain html.
<div>
<h3>#${props.displayText}#</h3>
${toDisplay}
</div>
Now ParentComponent
does technically contain some html -- the children
it's sending down to the first MyComponent. That's not Âideal -- perhaps we should make a HelloWorldComponent
-- but html children are almost forgivable. They are the exception that proves the general rule.
Past html to be sent and rendered in the leaf, though, no html in a branch!
Best practice: CSS
CSS is an interesting situation. The first rule for CSS in Preact is no inline CSS in preact! We generally want to use stylesheets to assign styles.
We can either hardcode class names into a leaf component or we can pull classes out of our props, if that's useful.
Let's add some css to our index.html
file, at least initially inline right below the existing title
tag.
<title>Hello, Preact World!</title>
<style>
p {
background-color: rgb(250, 250, 250);
}
.emph {
background-color: yellow;
}
.purple-text {
color: purple;
}
</style>
If you reload the page after just that, you'll notice that the paragraph with "This is some normally-sized text." in it has already adopted a grey background, just like you'd expect. This is why we can, say, introduce bootstrap straightforwardly, just as we normally would with static html.
For some more dynamism, let's make so that we can pass in the .emph
class for one of our children MyComponent
s.
MyComponent.js
(function () {
window.MyComponent = function (props) {
var toDisplay = props.children || html`<p>This is some normally-sized text.</p>`;
return html`<div class=${props.class}>
<h3>#${props.displayText}#</h3>
${toDisplay}
</div>`;
};
})();
ParentComponent.js
(function () {
window.ParentComponent = function (props) {
return html`<div>
<${MyComponent}
displayText="${props.first}"
class="emph"
>
<b>hello, world</b>
</MyComponent>
<${MyComponent} displayText="${props.second}" />
</div>`;
};
})();
And if you wanted to pass in two classes, nothing fancy; it's just a string.
<${MyComponent}
displayText="${props.first}"
class="emph purple-text"
>
<b>hello, world</b>
</MyComponent>
Now look, I know I said don't use inline styles, but just so you can recognize when someone does, you do it similarly to class
, but you can now use objects too.
Here's an example for MyComponent
, but not from props
. Using props
is the same as you'd expect, however.
MyComponent.js
(function () {
window.MyComponent = function (props) {
var toDisplay = props.children || html`<p>This is some normally-sized text.</p>`;
var styles = {
width: "600px",
margin: "0 auto",
fontFamily: '"Helvetica Neue", "Arial", sans-serif',
fontStyle: "italic",
fontWeight: "100",
fontVariantLigatures: "normal",
fontSize: "2rem",
letterSpacing: "2px",
};
return html`<div class=${props.class}>
<h3>#${props.displayText}#</h3>
<div style=${styles}>${toDisplay}</div>
</div>`;
};
})();
Again, the interesting thing to note here is that you use an object. I've been able to use keys that both match the style attribute I want to set ("font-size"
) or camelCased versions like I have in this example (fontSize
).