DEV Community

Edoardo Dusi for SparkFabrik

Posted on • Originally published at tech.sparkfabrik.com on

Introduction to Qwik and the Power of Loaders and Actions

Qwik logo

Introduction

Qwik is a JavaScript framework for building web applications with a focus on speed. It's designed to be "the fastest possible first load experience" by prioritizing what's important and loading only what's needed. It achieves this by using a component-level code splitting and optimized server-side rendering. Qwik was created by Miško Hevery, the same mind that created the Angular framework at Google, as well as the original AngularJS.

The defining characteristic of Qwik is its instant-on interactivity. Unlike other frameworks, Qwik is resumable, meaning Qwik applications require zero hydration. This allows Qwik apps to be interactive instantly, regardless of the size of the application.

I recently used Qwik for a talk that I'm giving at Come to Code in Italy. I was impressed by how easy it was to get started with Qwik, how fast it was to build a simple application, and particularly in my brief experience I learned to use two of Qwik's server-side features, routeLoader$ and routeAction$, and I wanted to share my findings with you.

Understanding Loaders and Actions

One of the key features of Qwik is resumability, and this is achieved by serializing the state of the application into the HTML and sending it to the client. This means that data loading and state changes must happen on the server, and this is where our two functions come in.

Both are APIs included in Qwik City, a library that provides server focused features.

  • routeLoader$ is used to bind a route to a function that loads data. This is typically used for loading data when the application is bootstrapped or when the state of the application changes.
  • routeAction$ is used to handle form submissions, to change the state of the application, and to perform any side effect.

The power of these APIs is their ability to simplify the management of application state and data loading, and the encapsulation of the logic inside specific functions with a separation of concerns that makes the code easier to maintain and reason about. They also allow Qwik to optimize the loading of components and data, and make the application feel faster for the user.

Examples

Let's take a look at an example. Suppose we're building a web application that fetches and displays RSS feeds. We have a function fetchFeeds that fetches the feeds from a list of URLs, and a function sortAndLimitFeeds that sorts these feeds by date and limits the result to the latest 10 feeds.

async function fetchFeeds(feedUrls) {
  // Fetch and parse the feeds...
}

function sortAndLimitFeeds(feeds) {
  // Sort and limit the feeds...
}
Enter fullscreen mode Exit fullscreen mode

We can use routeLoader$ to bind these functions to a URL. This means that when the application is bootstrapped or the state of the application changes, Qwik will automatically call these functions to load the data.

import { routeLoader$ } from '@builder.io/qwik-city';

export const useRssContentLoader = routeLoader$(async () => {
    const feedUrls = ['https://tech.sparkfabrik.com/en/index.xml', ...];
    const feeds = await fetchFeeds(feedUrls);
    return sortAndLimitFeeds(feeds);
});
Enter fullscreen mode Exit fullscreen mode

We can then use this function in our component to load the data. routeLoader$ returns a Signal, so we can get the value simply by accessing the value property.

import { useRssContentLoader } from './rss-content-loader';

export default function RssContent() {
  const rssContent = useRssContentLoader();
  return (
    <div>
      {rssContent.value.map((feed) => (
        <div>{feed.title}</div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The $ at the end of the function is a boundary marker, and it tells the Qwik Optimizer where to break the code to create chunks, for lazy loading.

The logic inside the function is executed on the server, and the result is serialized and sent to the client. This means that the data is available immediately, and that's why the application is interactive instantly.

Now let's take a look at routeAction$. Suppose we want to add a button to our application that allows the user to add a link from a feed to his favorites. We can use routeAction$ to create a function that executes on the server when the user clicks the button.

import { user } from './user';
import { routeAction$ } from '@builder.io/qwik-city';

export const useAddToFavoriteAction = routeAction$((item) => {
  const link = item.url;
  user.favoriteLinks.push(link);
  return {
    success: true,
  }
});
Enter fullscreen mode Exit fullscreen mode

Now we can add the special component Form to wrap our items list and add a button that calls the useAddToFavoriteAction function when clicked.

import { useAddToFavoriteAction } from './add-to-favorite-action';
import { Form } from '@builder.io/qwik-city';

export default component$(() => {
    const addToFavoriteAction = useAddToFavoriteAction();
    return (
        <Form action={addToFavoriteAction}>
            <div>
                {rssContent.value.map((feed) => (
                  <div>
                      <div>{feed.title}</div>
                      <button name="url" value={feed.link}>Add to favorite</button>
                  </div>
                ))}
            </div>
        </Form>
    );
});
Enter fullscreen mode Exit fullscreen mode

So routeAction$ is exposing a URL that we can use to trigger an action on the server, via a form submission, and the logic executed on the server is the body of the function.

The item parameter that we pass to the function is an object representing the form data. In this case, it contains the property url with the value attribute of the button that was clicked, for example:

{
    url: 'https://tech.sparkfabrik.com/en/blog/qwik-and-the-power-of-route/',
}
Enter fullscreen mode Exit fullscreen mode

And since under the hood the Form component uses the native HTML <form> element, all of these will work without JavaScript enabled in the browser!

Conclusion

There are many other features in Qwik that I haven't covered in this article, but I hope this gives you a good idea of what Qwik is and how it works. When I started working with Qwik to set up a demo, I was expecting a React-like framework optimized for SSR/SSG, and I knew about its resumability concept, but seeing it in action, after developing my simple application and playing with it, looking at its performance, network requests, and the simplicity of the code, I was really impressed. Not to mention the developer experience, which is also great with the qwik new and qwik add CLI tools and the debugging in the browser in development mode, the many integrations that already exist and others that are coming, the wonderful documentation, and the community that is growing around it.

Qwik is ready for production, and I think it's a great choice for building web applications. Maybe I will cover some of its other features in future articles, so stay tuned!

Top comments (0)