Skip to content

How to Setup Storybook in a Qwik Project

Introduction

Storybook is a great tool for testing and visualizing your components in different states. In this article, we will see how to setup Storybook in a Qwik project.

Qwik

Qwik is a new JavaScript framework by Misko Hevery, creator of Angular, for building frontend browser applications. The major benefit of Qwik is its performance optimization, and this is achieved through zero loading, resumability, lazy loading, reduced rendering, scalability, and code once.

For more information on Qwik, you can check out the docs, github repo, and discord.

Project Set Up

To get started, we need to create a new Qwik app. We can do this by running the following command in our terminal:

npm create qwik@latest

Initialize Storybook

Storybook, unfortunately, doesn’t have a Qwik template yet because it is a new Framework. So the work around is to use the html template .

Using the command below, we can initialize Storybook:

npx storybook init --type html
bootstrapping app & installing dep

During the initialization, Storybook would ask to automatically install some optional dependencies such as; EslintPlugin and npm7, you can accept or reject it. To accept, type y and hit Enter to progress.

installing optional deps

Storybook would try to setup a default stories folder, which is based on the –type template we choose, which is html. It is not compatible with the Qwik compiler and will result in compilation errors, so we will have to delete it to avoid running into such errors.

default stories folder

Project Structure

Our project structure is already setup by Qwik, and Storybook initialization has created a .storybook folder. But we need to make some changes to the Storybook file extension since our project is in TypeScript. This is a snippet of the folders in our project:

        ├── .storybook
            ├── main.ts
            ├── preview.ts
            └── preview-head.html
        ├── public
        ├── vite.config.ts
        └── src

Configuring Storybook

Since Qwik runs on Vite, we need to set up the viteFinal function in our main.ts file, which will give us the config that we will use to register our Qwik Vite plugin. Add this line of code in the configuration object:

      viteFinal: async (config, options) => {
        const { qwikVite: qwikVite } = await import('@builder.io/qwik/optimizer');
        config.plugins?.unshift(qwikVite());
        return config;
      },

In the preview.ts file, this is where we configure how Storybook renders our stories. We need to execute Qwikloader. This will help in registering global browser events, and much more Qwik related benefits. We will replace the content in the file with the code below:

    import { JSXNode } from '@builder.io/qwik';
    import { QWIK_LOADER } from '@builder.io/qwik/loader/index';
    import { render } from '@builder.io/qwik';
    import '../src/global.css';
    eval(QWIK_LOADER);

    export const decorators = [
      (Story: () => JSXNode) => {
        const parent = document.createElement('div');
        const jsxNode = Story();
        render(parent, jsxNode);
        return parent;
      },
    ];

This solution was found in this discussion: How to do component testing with Qwik?.

I believe when the Qwik Storybook type template becomes available these configurations will be there by default.

Now we are done with the configuration, let's run storybook and see what we have:

    npm run storybook

Creating Stories

We can create our first story, we will create a story for our Qwik app component. We will create this story for our default Qwik Header component. I modified the Header component to accept a menus props:

    import { Meta } from '@storybook/html';
    import Header, { HeaderProps } from './header';

    export default {
      title: 'Header',
    } as Meta;

    const Template = (args: HeaderProps) => <Header menus={args.menus} />;

    export const Demo: any = Template.bind({
        menus: []
    });

    Demo.args = {
        menus: [
            {
              name: 'Docs',
              link: 'https://qwik.builder.io/docs/components/overview/',
            },
            {
              name: 'Examples',
              link: 'https://qwik.builder.io/examples/introduction/hello-world/',
            },
            {
              name: 'Tutorials',
              link: 'https://qwik.builder.io/tutorial/welcome/overview/',
            },
          ]
    };

Conclusion

In this article, we saw how to setup Storybook in a Qwik project. We also saw how to create our first story. I hope you enjoyed this article. Thanks for reading.

If you don't want to do these steps yourself, check out our starter.dev Qwik kit that already has Storybook enabled for your use here.

A link to the project repo can be found here.

If you have any questions or run into any trouble, feel free to reach out on Twitter.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

Announcing Qwik for starter.dev cover image

Announcing Qwik for starter.dev

Hello there! Today, I'm so excited to introduce our new Qwik starter kit, which comes with 4 different technologies configured, and is ready for use. These technologies are: Qwik City, Storybook, Tailwind CSS, and GraphQL. In this short article, we will talk about the core framework, the routing, example apps to get you started, the tools and technologies used in this kit, and how it is a great out-of-the-box starter for your next Qwik project. Qwik Qwik is a new JavaScript framework by Misko Hevery, creator of Angular, for building frontend browser applications. The major benefit of Qwik is its performance optimization, which features resumability and lazy loading. Today we will be focusing on the Qwik framework starter kit. Technologies Used in the Starter Kit - Qwik City The kit is configured to make use of Qwik City, which offers directory-based routing (you don’t have to worry about colocation) and much more. Qwik City is a meta-framework of Qwik. It brings an opinionated and performant way to build sites at scale. - Storybook Storybook is a frontend workshop for building UI components and pages in isolation, testing, and documentation. It is open source and free. It is a great tool for testing and visualizing your components in different states. Qwik currently doesn’t have out-of-the-box support for Storybook, but we have figured out a way to configure it for Qwik. For more information on how we set up Storybook in Qwik, visit here. - Tailwind CSS Because Tailwind CSS is one of the most loved CSS libraries, we also included in this kit. Tailwind CSS is an open-source CSS framework. There is no special configuration to use Tailwind CSS in your Qwik app. You can use module CSS, or follow along with the kit (i.e we export the CSS classes as a variable). We will still achieve the same result, and make our code a bit cleaner, unlike when we write the CSS classes in the JSX directly. - GraphQL GraphQL is another great tool that was added to the kit. This gives you an out-of-the-box configuration to start making API calls to your GraphQL backend. GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. How to Get Started To get started, visit the Qwik starter.dev page for all the steps needed to create a new application with our starter.dev CLI, and run the application on your local system. Example Apps in the Starter Kit In this starter kit, we have two main components; - Counter - Greeting In the Counter component, we see how we can utilize the useStore hook provided by Qwik to store our counter state. In the Greeting component, we made use of the GraphQL config in the starter kit. The app sends a query request with the value entered in the input field, which is then returned back as a response from the API. With all of these configurations and code samples, we believe that with this Qwik starter kit, you can start building your production-scale application. Conclusion Qwik is still in its early days, and we will continue to update as we learn more. We welcome everyone to take a look, and if you have any questions or run into any trouble feel free to join the discussions going on at starter.dev or on our Discord....

Unit Testing Qwik Components cover image

Unit Testing Qwik Components

Unit Testing Qwik Components Qwik is a new, superfast JavaScript framework from builder.io. Created by Miško Hevery, the author of AngularJS, Qwik aims to deliver instant loading web applications of any size or complexity through resumability. This is accomplished partially by delaying the execution and download of JavaScript for as long as possible while providing an excellent developer experience. > "You know React? You know Qwik." > — qwik.builder.io Given Miško's background as the creator of AngularJS, it's interesting that writing Qwik feels very similar to writing React. It even has a React compatibility mode. Personally, I have experience using mostly Angular, but I found that developing with Qwik was very smooth and enjoyable. So, although I am definitely hesitant to jump on new technologies, after attending Miško's workshop, browsing through all the resources at framework.dev tinkering with Qwik, and building a couple of apps with it, I became a fan. However, being a responsible software developer, I then wanted to add unit tests to my Qwik app. And because Qwik is relatively new, I found out there was little documentation on how to do this. Furthermore, until recently, there were no tools to easily set up and interact with Qwik components in unit tests. But that didn't stop me. I explored, asked, and waited for a PR to be merged, and now I am proud to present a comprehensive guide to unit testing Qwik apps. So, without further ado, let's dive into setting up our testing environment. Configuring a Testing Environment There are several options when it comes to choosing a testing framework, and I have even experimented with using Jest, as it is the popular choice for unit testing. However, since Qwik uses Vite, I found that using Vitest was the most straightforward and efficient option. Vitest is specifically designed to be the go-to test runner for Vite projects, which makes it a great fit. If you are familiar with Jest, you will be happy to know that the Vitest API is very similar, so you won't have to learn much to use it effectively. Before moving further to the actual setup, let's make sure you have a Qwik project to test. If you don't yet, you can simply create one by running npm create qwik@latest`. Now, let's set up vitest in our Qwik project. It takes 3 easy steps: 1. Install Vitest as a dev dependency by running npm i vitest --save-dev` from the root of your project. This will update the __devDependencies__ array in your __package.json__ file. 2. Update your vite.config.ts__ file to include a test configuration in the `defineConfig` function. You can start with an empty object for now, but you can always add configuration options later on. Your __vite.config.ts__ should look something like this: ` JavaScript // ./vite.config.ts import { defineConfig } from 'vite'; import { qwikVite } from '@builder.io/qwik/optimizer'; import { qwikCity } from '@builder.io/qwik-city/vite'; import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig(() => { return { plugins: [qwikCity(), qwikVite(), tsconfigPaths()], preview: { headers: { 'Cache-Control': 'public, max-age=600', }, }, test: {}, // this is the config entry we are adding }; }); ` 3. Update the "scripts" section of your package.json__ file to include commands for running vitest once, running it in watch mode, and for generating coverage reports. Your "scripts" section should look something like this: ` JSON "scripts": { ... "test": "vitest --run", "test.watch": "vitest", "coverage": "vitest run --coverage" } ` After completing these steps, you should be able to run npm run test` and `npm run coverage` from your terminal to use Vitest. Note that if you run npm run test` and you don't have any tests yet, which is likely the case at this point, you will see the message `No test files found, exiting with code 1`. Don't worry. We are about to add some tests shortly. Writing Tests By default, vitest will look for files with names that match the pattern '**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}`. In a Qwik project that uses TypeScript and returns JSX elements, it makes sense to use the `*.spec.tsx` or `*.test.tsx` pattern. I prefer to use the`.spec` suffix. But `.test` works just as well, and is equally valid. Feel free to choose the one that you like best. To proceed with writing a unit test, we will need a component to test. For this example, we will use a simple counter component that has a div` that displays a number and a `button` that increments it. To create the component, follow these steps: 1. Go to the src/components__ directory, and create a new folder called __counter__. 2. Inside the counter__ folder, create a new file called __counter.tsx__ and add the following code: ` TypeScript import { component$, useStore } from "@builder.io/qwik"; export const Counter = component$(() => { const state = useStore({ count: 0, }); return ( state.count++}> + {state.count} ); }); ` 3. Next to the counter.tsx__ file, create a new file called __counter.spec.tsx__ file next to it. This file will contain the unit tests for the Counter component. Now that we have our component and our test file, we can proceed to write our unit tests. Let's open counter.spec.tsx__ and add some boilerplate along with a simple test to verify we have everything set up correctly: ` TypeScript import { describe, expect, it } from "vitest"; import { Counter } from "./counter"; describe("Counter component", function () { it('should assert true', async () => { // this should always pass expect(true).toBe(true); }); }); ` We have a describe` method that groups related tests together (in our case, we're grouping all the tests we will write for our Counter component), an `expect` method which is used to make assertions about the values returned by the code being tested, and an `it` method that defines an individual test. If we now run npm run test`, we should see the following output: ` Test Files 1 passed (1) Tests 1 passed (1) ` Writing a Simple Test Case Now that we verified that our dummy test work, let's actually test our component's behavior. First, we want to test that it renders correctly, so we will remove our dummy test and leverage the new createDOM method from @builder.io/qwik.testing` to render our Counter component inside the test: ` TypeScript import { createDOM } from "@builder.io/qwik/testing"; // import the createDOM method import { describe, expect, it } from "vitest"; import { Counter } from "./counter"; describe("Counter component", function () { it("should render", async () => { // create the component's DOM and get back the container and a render method const { screen, render } = await createDOM(); // call the render method with the JSX node of our Counter component as a parameter await render(); // get the div that displays the count from our container const countElement = screen.querySelector(".count"); // assert the displayed count is "0" which is the default value expect(countElement?.textContent).toBe("0"); }); }); ` Rerunning the test should result in a pass if we did everything correctly. Now we know our component does render and displays zero as the count. Testing Interactions "Great," you say, "but I want to test the component's logic, not just the initial render!". Well, you're in luck because the createDOM` method returns one more property - `userEvent` - that you can use to interact with the component's DOM. This allows us to simulate user interactions, such as clicking a button, and test whether the component responds correctly. For example, we can test whether our counter correctly increments the count by clicking the button. Here is the updated code example: ` TypeScript it("should increment on click", async () => { // we retrieve the userEvent` method along with `screen` and `render` const { screen, render, userEvent } = await createDOM(); // render the component await render(); // get the div that displays the count from our container const countElement = screen.querySelector(".count"); // assert the displayed count is "0" which is the default value expect(countElement?.textContent).toBe("0"); // pass a selector that matches the increment button as a first parameter // and the name of the event we want to trigger ("click") as the second parameter await userEvent("button.increment", "click"); // assert the displayed count is now incremented from 0 to 1 expect(countElement?.textContent).toBe("1"); }); ` With this additional test, we can be confident that our Counter component not only renders correctly, but also updates the count as expected when the increment button is clicked. Voilà, our component's behavior has been verified! Mocking Qwik Hooks So far, we have a test that verifies the component's logic, which is great! However, there may come a time when we need to mock parts of the component's logic to test certain scenarios. Unfortunately, here comes a little trade-off for the sake of Qwik's quickness. The component$ wrapper does not expose the internals of the component, so we cannot easily modify it for our tests. Fortunately, the most common thing we need to mock in Qwik components are hooks__ such as `useLocation()` or `useStore()`. We can do this by using `vi.mock`. As we can read in the Vitest docs, the `vi.mock` method takes two arguments: the path to the module that we want to mock, and a function that returns the mocked module. For example, if we want to modify the initial count in our useStore()` hook to be _1_, we can mock the entire @builder.io/qwik module and return the actual module with the modified initial value. We can use the JavaScript `bind` method to do this. The call to `vi.mock` can be placed anywhere in the code as it is hoisted and will always be called before modules are imported, but for clarity, we will put it in a `beforeAll` block: ` TypeScript import { createDOM } from "@builder.io/qwik/testing"; import { describe, expect, it, vi, beforeAll } from "vitest"; import { Counter } from "./counter"; beforeAll(() => { // mock useStore to start with count of 1 instead of 0 vi.mock("@builder.io/qwik", async () => { const qwik = await vi.importActual( "@builder.io/qwik" ); return { ...qwik, // return most of the module unchanged // leverage bind to set the initial state of useStore useStore: qwik.useStore.bind("initialState", { count: 1 }), }; }); }); describe("Counter component", function () { it("should increment on click", async () => { // we retrieve the userEvent` method along with `screen` and `render` const { screen, render, userEvent } = await createDOM(); // render the component await render(); // get the div that displays the count from our container const countElement = screen.querySelector(".count"); // assert the displayed count is "1" - the default value set by our mock expect(countElement?.textContent).toBe("1"); // pass a selector that matches the increment button as a first parameter // and the name of the event we want to trigger ("click") as the second parameter await userEvent("button.increment", "click"); // assert the displayed count is now incremented from 1 to 2 expect(countElement?.textContent).toBe("2"); }); }); ` Mocking the useLocation` hook is even simpler, as it is not expected to change during a test. In this case, we can directly return an object from the mock. Here is an example of how to mock the `useLocation` hook: ` TypeScript vi.mock("@builder.io/qwik", async () => { const qwik = await vi.importActual( "@builder.io/qwik" ); return { ...qwik, // return a hardcoded object for every useLocation call useLocation: { params: {}, href: "/mock", pathname: "mock", query: {}, } }; }); ` Conclusion As a newcomer to the world of JavaScript frameworks, Qwik offers a unique approach to building fast, efficient web applications. However, we shouldn't forget about the quality and robustness of our code. In this blog post, we provided a comprehensive guide on how to set up and write tests for your Qwik components. Setting up a testing environment for a Qwik app is relatively straightforward. Qwik uses Vite as its build tool, which means that the most efficient option for unit testing is to use Vitest, a test runner specifically designed for Vite projects. We showed how to install Vitest and update the Vite config file to include a test configuration, and went through examples of how to write tests for Qwik components, including how to mock hooks and test interactions with the DOM. By following the steps and examples in this post, you can easily add unit tests to your Qwik app, and ensure that your components are working correctly. This will give you confidence in your code, and allow you to build more complex and robust applications with Qwik. And if you found this article a bit overwhelming, do not worry, you can check out the Qwik starter kit which includes examples of already implemented tests. Have fun writing Qwik and reliable apps!...

Utilizing Browser Storage to Enhance User Experience in a Qwik Application cover image

Utilizing Browser Storage to Enhance User Experience in a Qwik Application

Introduction As front-end developers, we are always looking for ways to enhance the user experience of our applications. One of the ways we can achieve this is by utilizing the browser storage to store data, and implement some form of caching. Our web browser has provided us with different storage options to store data such as cookies, localStorage, and indexDB. In this article, we will be looking at how we can use storage to enhance a Qwik application. We are going to also explore hook technology in implementing this. Hooks 🪝 We will be extracting our storage function into a hook function that we can use in any part of our application. What are Hooks? Hooks are JavaScript functions that manage a component’s state and side effects by isolating them. That means we can isolate all the stateful logic into a hook and use it in any component. And just like in ReactJS, Qwik also allows us to create our own custom hooks. Project Set Up To get started, we need to create a new Qwik app. We can do this by running the following command in our terminal: `shell npm create qwik@latest ` You can also get additional tools and configurations using our starter.dev kit by running the command in your terminal: `shell npm create @this-dot/starter --kit qwik-graphql-tailwind ` After the project is created, we can run the following command to start the development server: `shell npm run dev ` Now we can open our browser and navigate to http://localhost:5143 to see our app running. The demo app we will be building is a simple form with an input field and a button. The input field will be used to enter a value, and the button will be used to reset the form and the storage state. We will also see how we apply this functionality in our Qwik GitHub showcase app to persist/catch data we fetch from the GitHub API. Storage Hook After the project is created, we can create a new folder in the src folder and name it “hooks”. In the hooks folder, we will create a new file and name it useLocalStorage.ts. In this file, we will create our useLocalStorage hook. `ts import { $, QRL, useClientEffect$, useStore } from "@builder.io/qwik"; export function useLocalStorage(key: string, initialState: any): [any, QRL void>] { const store = useStore({ value: initialState }); useClientEffect$(() => { try { // Get from local storage by key const item = window.localStorage.getItem(key); // Parse stored json or if none return initialState store.value = item ? JSON.parse(item) : initialState; } catch (error) { // If error also return initialState console.log(error); store.value = initialState; } }); const setValue$ = $((value: any) => { try { // Save state store.value = value; // Save to local storage if (typeof window !== "undefined") { window.localStorage.setItem(key, JSON.stringify(value)); } } catch (error) { // A more advanced implementation would handle the error case console.log(error); } }); return [store, setValue$]; } ` Let me explain what is happening here: - The useStore` hook is used to store the value. We initially set this value to the initial state value passed to our custom hook. - In the useClientEffect$` hook, which runs on the client, we try to update our store value with the value from the localStorage based on the `key` passed to the hook. If the value is found, we set its parsed value to the store value. If not, we set the store value to the initial state passed to the hook. We also catch any error that might occur, and set the store value to the initial state. - We also have a setValue$` function that is used to set the value of the store and also save it to the localStorage. The `setValue$` function is wrapped in a `$` function, which is used to create a QRL function- a Qwik optimizer marker function. For more information on QRL, visit the docs. - We also return store and the setValue$` function. This exposes them to be consumed in our components. We don't want to return the value of the store or else it will lose its reactivity. Form Component In the components folder, we will create a new folder called form, which will contain an index file that will export our Form component. The form will be expecting two props; value` and `setValue`, which are from the `useLocalStorage` hook. `ts import { component$, QRL } from "@builder.io/qwik"; interface IProps { value: string, setValue: QRL void> } export default component$((props: IProps) => { return ( { const input = e.target as HTMLInputElement; props.setValue(input.value) }} placeholder="Name" aria-label="name" /> ); }); ` In our index file, we will import the useLocalStorage` hook and the Form component, and our updated index file will look like this: `ts import { component$ } from '@builder.io/qwik'; import Form from '/components/form'; import { useLocalStorage } from '/hooks/useLocalStorage'; export default component$(() => { const [value, setValue] = useLocalStorage("name", "Guest"); return ( Welcome to Qwik {value} ⚡️ window.location.reload() } /> ); }); ` We can now see what our simple app looks like in the browser, and we can interact with it. By entering a value in the input field and reloading the page, we can see that the value persists. Next, we want to implement this hook in our Qwik GitHub showcase app to persist the gists data we fetch from the GitHub API and persisting it in the localStorage. We transform our fetch gists logic from this: `ts useClientEffect$(async () => { const abortController = new AbortController(); const response = await fetchGIst(abortController); updateGists(store, response); }); ` to this; `ts useClientEffect$(async () => { if (cachedGists.value) { store.isLoading = false; store.data = cachedGists.value; } else { const abortController = new AbortController(); const response = await fetchGIst(abortController); updateGists(store, response, setGists); } }); ` So we check if we have cached gists in the Storage. If we do, we set the store data to the cached gists. If not, we fetch the gists from the GitHub API and update the store data with the response. We also pass the setGists` function to the `updateGists` function, which is the `setValue$` function from the `useLocalStorage` hook. This function is used to set the value of the store, and also save it to the localStorage. Conclusion In this article, we were able to understand why we need to improve user experience with a focus on persisting data. We also saw how to create a hook in a Qwik framework, and use our hook to persist data in the localStorage. A link to the project repo can be found here and for our Qwik starter.dev showcase app, here. If you have any questions or run into any trouble, feel free to reach out on Twitter or on our Discord....

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels cover image

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels

In this episode of the engineering leadership series, Kathy Keating, co-founder of CTO Levels and CTO Advisor, shares her insights on the role of a CTO and the challenges they face. She begins by discussing her own journey as a technologist and her experience in technology leadership roles, including founding companies and having a recent exit. According to Kathy, the primary responsibility of a CTO is to deliver the technology that aligns with the company's business needs. However, she highlights a concerning statistic that 50% of CTOs have a tenure of less than two years, often due to a lack of understanding and mismatched expectations. She emphasizes the importance of building trust quickly in order to succeed in this role. One of the main challenges CTOs face is transitioning from being a technologist to a leader. Kathy stresses the significance of developing effective communication habits to bridge this gap. She suggests that CTOs create a playbook of best practices to enhance their communication skills and join communities of other CTOs to learn from their experiences. Matching the right CTO to the stage of a company is another crucial aspect discussed in the episode. Kathy explains that different stages of a company require different types of CTOs, and it is essential to find the right fit. To navigate these challenges, Kathy advises CTOs to build a support system of advisors and coaches who can provide guidance and help them overcome obstacles. Additionally, she encourages CTOs to be aware of their own preferences and strengths, as self-awareness can greatly contribute to their success. In conclusion, this podcast episode sheds light on the technical aspects of being a CTO and the challenges they face. Kathy Keating's insights provide valuable guidance for CTOs to build trust, develop effective communication habits, match their skills to the company's stage, and create a support system for their professional growth. By understanding these key technical aspects, CTOs can enhance their leadership skills and contribute to the success of their organizations....