Conditional JSX in Astro
- Published on:
- Categories:
- Astro 7, Process 9
- Current music:
- Will Driving West —
Walls
- Current drink:
- Lemongrass, Ginger & Black Pepper tea
This is me documenting the way I am handling some of the more complex conditional rendering in Astro’s JSX. Well, not strictly JSX — but its “Component Template” part of .astro components and assigning them to variables.
The Problem
In React/Preact, inside a .jsx/.tsx it is possible to save snippets of JSX code to variables and then use them conditionally, like this:
const MyComponent ({ linkText, isDiv = false }) => {
const someContent = (
<a href="#hello">{linkText}</a>
);
return (
<div class="wrapper">
{isDiv ? (
<div class="inner">
{someContent}
</div>
) : (
<span class="inner">
{someContent}
</span>
)}
</div>
);
}
This example is nonsensical, but you should get an idea: we’re able to define some JSX code in the function body, and then reuse it inside various conditionals in the component’s return.
This is very useful when you have some complex content, but then in different setups it could be rendered in different contexts, or in a different order.
In .astro components, if you’re coming from React or Preact, it is easy to misunderstand the component’s Script part as being the function’s body, and the Template as the return.
But the problem: it is not possible to save snippets of HTML in the Script part! So it can be pretty awkward to work with more complex HTML structures.
If you were to try writing something like this:
---
const { linkText, isDiv = false } = Astro.props;
const someContent = (
<a href="#hello">{linkText}</a>
);
---
<div class="wrapper">
{isDiv ? (
<div class="inner">
{someContent}
</div>
) : (
<span class="inner">
{someContent}
</span>
)}
</div>
You would get an error:
[ERROR] Unexpected ";"
The Solution
The solution for this that I found: good old IIFE, or “Immediately Invoked Function Expressions”.
Astro allows JavaScript Expressions inside the Temlpate part. Those are usually there for conditions, maps, and so on. And — they support HTML inside, so you could do things like {list.map (item => <li>{item}</li>)} that you could be used to.
But that means that nothing stops us from doing this:
---
const { linkText, isDiv = false } = Astro.props;
---
{(() => {
const someContent = (
<a href="#hello">{linkText}</a>
);
return (
<div class="wrapper">
{isDiv ? (
<div class="inner">
{someContent}
</div>
) : (
<span class="inner">
{someContent}
</span>
)}
</div>
);
})()}
This works! The HTML is correctly parsed and assigned to the someContent variable, which then can be reused as you wish.
A good way to think about it if you’re approaching to this from React/Preact mindset:
-
Treat the
Scriptpart as the outside of your component’s function body. All the imports, helper functions, and static calculations could go there. -
The function body of IIFE is now the place where you could define any reusable snippets of JSX. Nice.
-
Its return is what will be rendered. That means that you could also benefit from early returns, and simplify your conditions!
-
You could even put the
Astro.propsas the argument of that IIFE, and then destructure props as if you were authoring a React component… But why?
Offtopic: Upgrading Astro
I love that Astro is prebuilt statically by default, so all this logic will exist only before build, and the final HTML will be clean and nice.
Before writing this post, I upgraded Astro from 4.14.4 to 5.16.2 and did not encounter almost any problems… Or so I wrote initially, only to find out I couldn’t publish my blog, even though npm run dev worked perfectly.
There were a few issues:
Random LightningCSS Warnings
I noticed that when running npm run dev I got the following warnings from LightningCSS:
[WARN] [vite] [vite:css][lightningcss] …
Solution: I suppressed them by passing logLevel: 'error' to vite inside astro.config.mjs.
Node.js Version Requirement
I use Netlify, and apparently it runs an older version of Node.js by default, and Astro 5 stopped supporting it.
Solution: add a supported node version via .nvmrc.
highlighter.codeToHtml is not a function
Ooof, this one was a hard one! I found one post that mentioned this issue: “Astro Code Toggle Component” by Daniel Corin, but there the author did not debug what exactly is going on, stopping at noticing that the issue was in the Astro’s Code component, and reimplementing the highlighter on their own.
Debugging it myself, and logging what’s going on, I noticed that the highlighter in question had a different method available, which worked. The types did complain — and this seemed like an issue of something in dependencies.
Looking at my package-lock.json I found that the library that should provide that highlighter was @astrojs/markdown-remark, and it was not on the latest version. Trying to force the latest version in my package.json shown the reason why it wasn’t the latest: another package — @astropub/md by Jonathan Neal had an earlier @astrojs/markdown-remark version as its peer dependency!
The solution: override the dependency specifically for @astropub/md in my package.json while adding a newer version by default:
{
"dependencies": {
"@astrojs/markdown-remark": "^6.3.9",
},
"overrides": {
"@astropub/md": {
"@astrojs/markdown-remark": "5.3.0"
}
}
}
Oof, this worked! Ideally, I guess, it might be time to remove the @astropub/md — I only use it to render markdown for RSS, and I think there is now a better way to do it through Astro, so maybe one day (when my solution stops working?) I’ll return to this.
Ah, and I had to add --legacy-peer-deps to the NPM_FLAGS environmental variable in Netlify to make it run npm install --legacy-peer-deps as otherwise it was erroring out.
That’s It
I also looked at my other dependencies, noticed that there was Preact, which I experimented for a while years ago, and decided to remove it and its Astro integration as well — after all, I did not end up using it for anything.
I hope that this post will help you if you’re working with Astro, and, like me, is unfortunate to come to it with React preconceptions. Although, even if not, I think this IIFE pattern inside Astro templates is still fun!
And — debugging the build issues took much longer than I wanted, and three times as long as it took writing the rest of this post. Oh hey, it is modern front-end for you.