DEV Community 👩‍💻👨‍💻

Cover image for The Power of Svelte Markup or When Less is More
Johnny Jarecsni
Johnny Jarecsni

Posted on • Updated on

The Power of Svelte Markup or When Less is More

Intro

This is just a quick post about a revelation I have made today about the profound consequences of how Svelte markups work. Or more precisely, how they do not work...

Long Story Short

I am new to Svelte, coming from a React background and that means that I will try things in a 'Reacty' way first (why, if we have words like 'cupboardy', 'Reacty' is fine), just like anyone else, first trying stuff that is familiar.

However Svelte is radically different from React in many ways, and one of these areas is its approach to markup (you know the HTML like thing with the angle brackets).

The Problem

Lets just say I have a bunch of meet-up event objects in an array, which I want to render as a list. There is one tiny twist, when there are multiple events on the same day, I want to collapse these events under the same date (only render the date in front of the very first event on any given day).

React Instincts Kick in

So I thought, ok fine, I can just use an #if to emit the date if it is different from the 'currentDate' that we hold in a variable.

{#each upcomingEvents as event}
   {#if event.date !== currentDate }
      <label>{event.date}</label>
   {/if}
   <label>{event.time}</label>
   <label>{event.title}</label>
   <label>{event.group}</label>
{/each}
Enter fullscreen mode Exit fullscreen mode

Looks promising right? Ok let's add the variable

<script>
let currentDate = null;
<script>
Enter fullscreen mode Exit fullscreen mode

It is again dead easy, we just declare the variable, and since it is null, the very first item in the array will have a different date, so that is good as we will render it nicely.

Now there is just one more tiny problem to solve, updating this variable with the lastly rendered event's date. Should not be too difficult right?

Try something like:

{#each upcomingEvents as event}
   {#if event.date !== currentDate }
      <label>{event.date}</label>
   {/if}
   <label>{event.time}</label>
   <label>{event.title}</label>
   <label>{event.group}</label>
   {currentDate = event.date}
{/each}
Enter fullscreen mode Exit fullscreen mode

(As an astute reader pointed out this is not idiomatic React. Yes true, we would not do an assignment like that from JSX, but we also don't have 'each' (or templating keywords) in React, where you'd probably map the array to JSX. Lets just say the assignment to currentDate is the next logical option that was in my mind (given we don't do mapping to markup in Svelte!) and this article is showing how the revelations unfolded for me, so you get to see the work in progress :)

Svelte Markup != JSX

Too bad, it does not work. And here is the deal - you cannot do this the React way. React's JSX is two-way, code and markup are interchangeable. You can return JSX from functions, you can switch to code entirely within your JSX (that is why you can just call Array.map() to transform a piece of data to markup in the middle of your JSX expression).

Svelte markup is markup only, or we could say it is one directional. It can render data, but there is no way to execute code from markup (this is exactly why Svelte needs a few templating keywords like #each and #if, to be able to apply some minimal logic to rendering).

So how can this problem be solved with Svelte then?

A Moment of Revelation

The answer is separation of concerns. Separating presentation / rendering concerns (done by markup) and model transformations (done by code).

Therefore, to achieve what we want, we will need to create a derivation of our primary model object (the array of event objects) such that our events are grouped by date, and then render this model by a simple markup:

let upcomingEventsGrouped; 
$: {
   let currentDate = null;
   upcomingEventsGrouped = [];
   upcomingEvents.forEach(({date, time, title, group}) => {
      const _date = (date !== currentDate ? date : null);
      upcomingEventsGrouped.push({
         time, title, group, date: _date 
      });
      currentDate = date;
   });
   upcomingEventsGrouped = upcomingEventsGrouped;
}
Enter fullscreen mode Exit fullscreen mode

It is worth noting at this point, that this model derivation is declared reactively (it is recomputed every time the underlying data, upcomingEvents in this case, changes). That's achieved by using '$:' - isn't that just wonderful?!

Here is the template to render the derived data:

{#each upcomingEventsGrouped as event}
   {#if !!event.date }
      <label>{event.date}</label>
   {/if}
   <label>{event.time}</label>
   <label>{event.title}</label>
   <label>{event.group}</label>
{/each}
Enter fullscreen mode Exit fullscreen mode

Really simple, just mapping the data attributes to markup elements, really easy to read!

And here's a pieace of event code that tests adding a new event in the upcomingEvents array:

function addEvent(e) {
   upcomingEvents = [...upcomingEvents, {
      date: '20/06/2020',
      time: '18:00',
      title: 'Templating with Svelte 2.',
      group: 'Svelte Rookies'
   }];
}
Enter fullscreen mode Exit fullscreen mode

Note that we need to use assignment in some form wherever we update an array otherwise Svelte will not pick up on the change!

Finally

I think Svelte's take on the markup may come across first as limiting and less powerful than React's JSX, however there is huge power in this - the power of simplicity.

React JSX lends itself for complicated solutions (taking on responsibilities of the view model), while Svelte's markup forces you to offload these concerns to your script element, resulting in a simple 1:1 mapping when it comes to rendering the model.

Thanks for reading this article, hope you enjoyed it and if you have anything to add/correct etc, please do leave a comment!

Top comments (1)

Collapse
 
azaidi93 profile image
Ali Zaidi

Nice one Janos 👏🏽

🌚 Browsing with dark mode makes you a better developer.

It's a scientific fact.