Qwik
Introduction to QuikJS
Qwik is a modern JavaScript framework designed to provide an efficient and familiar development experience for React developers. It is notable for its resumable nature, which means it avoids eager JavaScript execution and hydration. Qwik is well-suited for building web applications that perform exceptionally well on the edge, making it ideal for a wide range of use cases.
In this guide, we’ll walk through the process of getting started with Qwik, creating a Qwik project, and writing a simple component.
Prerequisites
Before you get started with Qwik, ensure that you have the following prerequisites installed on your development machine:
- Node.js version 16.8 or higher
- Your preferred code editor or IDE (Visual Studio Code is recommended)
Additionally, you may find it helpful to read through the “think qwik” documentation for a deeper understanding of the Qwik framework.
Creating a Qwik Project
To create a Qwik application, you can use the Qwik CLI, which generates a blank starter project. Follow these steps to create a new Qwik project:
Open your terminal or command prompt.
Choose your preferred package manager (npm, pnpm, yarn, or bun) and run one of the following commands:
npm create qwik@latest
pnpm create qwik@latest
yarn create qwik
bun create qwik@latest
The Qwik CLI will guide you through an interactive menu where you can set your project name, select a starter template, and choose whether to install project dependencies.
After completing the setup, start the development server using one of the following commands, depending on your chosen package manager:
npm start
pnpm start
yarn start
bun start
This will launch your Qwik application’s development server, making it accessible in your browser.
Writing a Qwik Component
In Qwik, components are at the core of your application. Let’s create a simple Qwik component step by step. In this example, we’ll build a basic joke application.
Step 1: Create a Route
Qwik uses directory-based routing. To start, create a new directory within your project’s src/routes directory. For example, let’s create a joke directory:
mkdir src/routes/joke
Inside this directory, create an index.tsx file. This file will serve as the default component for your route:
// src/routes/joke/index.tsx
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <section class="section bright">A Joke!</section>;
});
Step 2: Load Data
To load data for your component, you can use route loaders. In this case, we’ll load a random joke from an external API. Update your src/routes/joke/index.tsx file as follows:
// src/routes/joke/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const useDadJoke = routeLoader$(async () => {
const response = await fetch('https://icanhazdadjoke.com/', {
headers: { Accept: 'application/json' },
});
return (await response.json()) as {
id: string;
status: number;
joke: string;
};
});
export default component$(() => {
const dadJokeSignal = useDadJoke();
return (
<section class="section bright">
<p>{dadJokeSignal.value.joke}</p>
</section>
);
});
This code introduces the concept of route loaders. The useDadJoke function loads a random dad joke from an external API and returns it as a reactive signal.
Step 3: Styling
To style your component, you can create a CSS file and associate it with your component. Create a new CSS file named index.css
inside the src/routes/joke
directory:
/* src/routes/joke/index.css */
p {
font-weight: bold;
}
form {
float: right;
}
Now, import and use these styles in your component:
// src/routes/joke/index.tsx
import { component$, useStylesScoped$ } from '@builder.io/qwik';
import styles from './index.css?inline';
export default component$(() => {
useStylesScoped$(styles);
// ... rest of the component code
});
Step 4: Preview Your Application
To preview your Qwik application in production mode, you can build the project and run it. Use the following command to create a production build:
npm run preview
Your application will be built for production, and you can access it on a different port. Interacting with the application in this mode should show improved performance.
Congratulations! You’ve created a simple Qwik component and learned some fundamental concepts of the Qwik framework. Qwik’s resumable and edge-focused nature makes it a powerful choice for modern web development. You can continue building upon this foundation to create more complex applications with Qwik.
Writing Components in Qwik
Components are the fundamental building blocks of Qwik applications, serving as reusable units of UI code. Qwik components have several unique features and best practices that differentiate them from traditional component models:
Lazy Loading
Qwik components are automatically broken down into lazy-loaded chunks by the Qwik Optimizer. This feature enables efficient loading of components as they are needed, improving application performance.
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <div>Hello World!</div>;
});
The component$ function is crucial in enabling lazy loading. The trailing $ signifies to the Optimizer that this component can be loaded independently. Without the $, the component would always load when its parent component is loaded.
Composing Components
Qwik components are designed to be composable. You can nest components within other components to create more complex UI structures. Importantly, Qwik components are already lazy-loaded, so you don’t need to manually import child components.
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<>
<p>Parent Text</p>
<Child />
</>
);
});
const Child = component$(() => {
return <p>Child Text</p>;
});
Managing State
Qwik provides state management through the useSignal hook. Components can reactively render based on state changes. Components are designed to re-render efficiently, only when necessary.
import { component$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const count = useSignal(0);
return (
<>
<p>Count: {count.value}</p>
<button onClick$={() => count.value++}>Increment</button>
</>
);
});
Props
Props allow you to pass data from parent components to child components. Props are accessible through the props argument in the component$ function.
import { component$ } from '@builder.io/qwik';
interface ItemProps {
name?: string;
quantity?: number;
description?: string;
price?: number;
}
export const Item = component$<ItemProps>((props) => {
return (
<ul>
<li>name: {props.name}</li>
<li>quantity: {props.quantity}</li>
<li>description: {props.description}</li>
<li>price: {props.price}</li>
</ul>
);
});
export default component$(() => {
return (
<>
<h1>Props</h1>
<Item name="hammer" price={9.99} />
</>
);
});
Using TypeScript, you can provide explicit type information for props to enable type checking.
Default Props
You can use destructuring with props to provide default values, making components more flexible and resilient to missing or undefined props.
interface Props {
enabled?: boolean;
placeholder?: string;
}
export default component$<Props>(({ enabled = true, placeholder = '' }) => {
return <input disabled={!enabled} placeholder={placeholder} />;
});
Rendering on Reactivity
Qwik components are reactive, automatically updating in response to state changes. Components can react to two types of updates: changes that affect DOM text or attributes, and changes that cause structural DOM modifications.
Components are invalidated when state changes, and they are added to an invalidation queue that is rendered on the next requestAnimationFrame. This mechanism helps optimize component rendering.
Getting Hold of DOM Elements
You can use the ref attribute to obtain references to DOM elements within Qwik components. This is useful for tasks such as measuring element dimensions, accessing computed styles, or interacting with third-party libraries.
import { component$, useVisibleTask$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const width = useSignal(0);
const height = useSignal(0);
const outputRef = useSignal<Element>();
useVisibleTask$(() => {
if (outputRef.value) {
const rect = outputRef.value.getBoundingClientRect();
width.value = Math.round(rect.width);
height.value = Math.round(rect.height);
}
});
return (
<section>
<article ref={outputRef} style={{ border: '1px solid red', width: '100px' }}>
Change text value here to stretch the box.
</article>
<p>
The above red box is {height.value} pixels high and {width.value} pixels wide.
</p>
</section>
);
});
Accessing Elements by ID Across Server and Client
To access elements by their ID consistently across server-side rendering (SSR) and client-side operations, use the useId function. This function provides a unique identifier that remains consistent between environments.
import { component$, useId, useSignal, useVisibleTask$ } from '@builder.io/qwik';
export default component$(() => {
const elemIdSignal = useSignal<string | null>(null);
const id = useId();
const elemId = `${id}-example`;
useVisibleTask$(() => {
const elem = document.getElementById(elemId);
elemIdSignal.value = elem?.getAttribute('id') || null;
});
return (
<section>
<div id={elemId}>
Both server-side and client-side console should match this id:
<br />
<b>{elemIdSignal.value || null}</b>
</div>
</section>
);
});
Lazy Loading with Qwik Components
To avoid transitive dependencies and improve bundling, refer to Qwik components through their lazy wrappers. The Optimizer ensures that lazy-loaded components are loaded only when needed.
import { component$ } from '@builder.io/qwik';
export const Child = component$(() => {
return <p>child</p>;
});
export const Parent = component$(() => {
return (
<section>
<Child />
</section>
);
});
export default Parent;
In this approach, the Optimizer transforms the code to prevent direct references between components, allowing bundlers to move components independently.
Inline Components
Qwik also supports inline components for lightweight markup. Inline components are bundled with their parent components, making them useful for small UI elements.
import { component$ } from '@builder.io/qwik';
// Inline component declared using a standard function.
export const MyButton = (props: { text: string }) => {
return <button>{props.text}</button>;
};
export default component$(() => {
return (
<p>
Some text:
<MyButton text="Click me" />
</p>
);
});
However, inline components have limitations. They cannot use use* methods like useSignal or project content with a
Qwik’s component model offers efficient, lazy loading, reactivity, and composability, making it a powerful choice for building modern web applications.
Creating a Todo App in Qwik
Step 1: Project Setup
Before creating the Todo App component, make sure you have a QwikJS project set up, as described in the previous guide. Once your project is ready, you can start building the Todo App.
Step 2: Create Components
In QwikJS, components are at the core of your application. You can create components for different parts of your Todo App, such as the Todo list, individual Todo items, and the input for adding new Todos.
Let’s start by creating a basic Todo list component. Create a new directory for your components, such as src/components, and inside that directory, create a file named TodoList.tsx:
// src/components/TodoList.tsx
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<div>
<h2>Todo List</h2>
{/* Todo items will be displayed here */}
</div>
);
});
Similarly, create components for Todo items and the input for adding new Todos:
// src/components/TodoItem.tsx
import { component$ } from '@builder.io/qwik';
export default component$(({ todo }) => {
return (
<div>
<input type="checkbox" checked={todo.completed} />
<span>{todo.text}</span>
<button>Delete</button>
</div>
);
});
// src/components/TodoInput.tsx
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<div>
<input type="text" placeholder="Add a new Todo" />
<button>Add</button>
</div>
);
});
Step 3: Managing State
To manage the state of your Todo App, you can use Qwik’s useSignal hook. Create a new file, such as src/state/todoState.ts, to manage the state of your Todos:
// src/state/todoState.ts
import { useSignal } from '@builder.io/qwik';
export const useTodoState = () => {
const [todos, setTodos] = useSignal([]);
const addTodo = (text) => {
const newTodo = { text, completed: false };
setTodos((prevTodos) => [...prevTodos, newTodo]);
};
const deleteTodo = (index) => {
setTodos((prevTodos) => prevTodos.filter((_, i) => i !== index));
};
return {
todos,
addTodo,
deleteTodo,
};
};
Step 4: Todo List Component
Now, let’s update the TodoList component to display the list of Todo items. Import the useTodoState hook and the TodoItem component:
// src/components/TodoList.tsx
import { component$ } from '@builder.io/qwik';
import { useTodoState } from '../state/todoState';
import TodoItem from './TodoItem';
export default component$(() => {
const { todos } = useTodoState();
return (
<div>
<h2>Todo List</h2>
<div>
{todos().map((todo, index) => (
<TodoItem todo={todo} key={index} />
))}
</div>
</div>
);
});
Step 5: Todo Input Component
Next, update the TodoInput component to allow users to add new Todos. Import the useTodoState hook:
// src/components/TodoInput.tsx
import { component$ } from '@builder.io/qwik';
import { useTodoState } from '../state/todoState';
export default component$(() => {
const { addTodo } = useTodoState();
const handleAddTodo = (e) => {
e.preventDefault();
const input = e.target.querySelector('input[type="text"]');
const text = input.value.trim();
if (text) {
addTodo(text);
input.value = '';
}
};
return (
<div>
<form onSubmit={handleAddTodo}>
<input type="text" placeholder="Add a new Todo" />
<button>Add</button>
</form>
</div>
);
});
Step 6: Putting it All Together
Finally, you can create a parent component that includes both the TodoList and TodoInput components to form the complete Todo App. Import these components and render them:
// src/App.tsx
import { component$ } from '@builder.io/qwik';
import TodoList from './components/TodoList';
import TodoInput from './components/TodoInput';
export default component$(() => {
return (
<div>
<h1>Todo App</h1>
<TodoInput />
<TodoList />
</div>
);
});
Step 7: Preview Your Todo App
Now that you’ve created the components and managed the state, you can preview your Todo App by rendering the App component in your Qwik application. Update your application’s routing to include the App component, and start the development server to see your Todo App in action.
With these steps, you’ve built a basic Todo App component in QwikJS. You can expand and customize this app further to add features like editing Todos, marking them as complete, or persisting the data to a server. QwikJS’s reactive and efficient nature makes it a powerful choice for building modern web applications.