Events

For a web application to be interactive, there needs to be a way to respond to user events. This is done by registering callback functions in the JSX template.

In the following example, the onClick$ attribute of the <button> element is used to let Qwik know that a callback () => store.count++ should be executed whenever the click event is fired by the <button>.

Notice that onClick$ ends with $. This is a hint to both the Optimizer and the developer that a special transformation occurs at this location. The presence of the $ suffix implies a lazy-loaded boundary here. The code associated with the click handler will not download until the user triggers the click event.

import { component$, useSignal } from '@builder.io/qwik';

export const Counter = component$(() => {
  const count = useSignal(0);

  return <button onClick$={() => count.value++}>{count.value}</button>;
});

In a real applications, the listener may refer to complex code. By creating a lazy-loaded boundary (with the $), Qwik can tree-shake all of the code behind the click listener and delay its loading until the user clicks the button.

Another option is passing QRLs as values for event listeners. For instance, the above example could also be written in the following way:

import { component$, useSignal, $ } from '@builder.io/qwik';

export const Counter = component$(() => {
  const count = useSignal(0);
  const incrementCount = $(() => count.value++);

  return <button onClick$={incrementCount}>{count.value}</button>;
});

Prevent default

Because of the async nature of Qwik, an event's handler execution might be delayed because the implementation has not been downloaded yet.

This introduces a problem when the event's handler needs to prevent the default behavior of the event. Traditional event.preventDefault() will not work, so instead use Qwik's preventdefault:{eventName} attribute:

import { component$ } from '@builder.io/qwik';

export const PreventDefaultExample = component$(() => {
  return (
    <a
      href="/about"
      preventdefault:click // This will prevent the default behavior of the "click" event.
      onClick$={(event) => {
        // PreventDefault will not work here, because handle is dispatched asynchronously.
        // event.preventDefault();
        singlePageNavigate('/about');
      }}
    >
      Go to about page
    </a>
  );
});

Synchronous event handling

In some cases, it is necessary to handle an event in the traditional way, because some APIs need be used syncronously. Drag and drop events for instance will not work otherwise.

To do this, we can leverage a useVisibleTask to programatically add an event listener using the DOM API directly.

import { component$, useVisibleTask$ } from '@builder.io/qwik';

export const DragAndDropExample = component$(() => {
  const elementRef = useSignal<HTMLElement>()
  useVisibleTask$(({cleanup}) => {
    if (elementRef.value) {
      // Use the DOM API to add an event listener.
      const listener = (event) => {
        event.dataTransfer.setData('text/plain', 'Hello World');
      };

      elementRef.value.addEventListener('dragstart', listener);
      cleanup(() => {
        elementRef.value.removeEventListener('dragstart', listener);
      });
    }
  });

  return (
    <div ref={elementRef}>
      Drag me!
    </div>
  );
});

Notice that using VisibleTask to listen for events in an anti-pattern in Qwik that will add a lot of unnecessary overhead, only use it when you have no other choice. 99% of the times, you should use JSX to listen for events: <div onClick$={...}>

Events and Components

Components are functions, not elements. Since DOM events do not exist naturally, it's possible to define custom events as props.

Now let's look at how to declare a child component that can be used with events as props.

import { PropFunction, component$ } from '@builder.io/qwik';

interface CmpButtonProps {
  onClick$?: PropFunction<() => void>;
}

export const CmpButton = component$((props: CmpButtonProps) => {
  return (
    <button onClick$={props.onClick$}>
      <Slot />
    </button>
  );
});

As far as Qwik is concerned, passing events to a component is equivalent to passing props.

In our example, we declare all props in the CmpButtonProps interface. Specifically, notice onClick$: PropFunction<() => void> declaration.

Then, when we want to use <CmpButton>, we can do the following:

<CmpButton onClick$={() => store.cmpCount++}>{store.cmpCount}</CmpButton>

Window and Document events

So far, we have discussed how to listen to events that originate from elements. There are events (for example, scroll and mousemove) that require that we listen to them on the window or document. For this reason, Qwik allows for the document:on and window:on prefixes when listening for events.

import { component$, useSignal } from '@builder.io/qwik';

export const EventExample = component$(() => {
  const scroll = useSignal(0);
  const mouse = useSignal({ x: 0, y: 0 });
  const clickCount = useSignal(0);

  return (
    <button
      window:onScroll$={(e) => (scroll.value = window.scrollY)}
      document:onMouseMove$={(e) => {
        const { x, y } = e;
        mouse.value = { x, y };
      }}
      onClick$={() => clickCount.value++}
    >
      scroll: {scroll.value}
      mouseMove: {mouse.value.x}, {mouse.value.y}
      click: {clickCount.value}
    </button>
  );
});

The purpose of the window:on/document: is to register an event at a current DOM location of the component but have it receive events from the window/document. There are two advantages to this:

  1. The events can be registered declaratively in your JSX.
  2. The events get automatically cleaned up when the component is destroyed (No explicit bookkeeping and cleanup is needed).

useOn Hooks

useOn

useOn hook will add a DOM based event listener at component level.

import { component$, useSignal, useOn, $ } from '@builder.io/qwik';

export const ClickableComponent = component$(() => {
  useOn(
    'click',
    $(() => {
      alert('Alert from Clickable Component.');
    })
  );
  return <div>click from other component 1</div>;
});

export const HoverComponent = component$(() => {
  const isHover = useSignal(false);
  useOn(
    'mouseover',
    $(() => {
      isHover.value = true;
    })
  );
  return <div>{isHover.value ? 'Now Hovering' : 'Not Hovering'}</div>;
});

export default component$(() => {
  return (
    <>
      <ClickableComponent />
      <HoverComponent />
    </>
  );
});

useOnWindow

useOnWindow hook will add a event listener to window.

import { component$, useOnWindow, $ } from '@builder.io/qwik';

export const Online = component$(() => {
  useOnWindow(
    'online',
    $(() => {
      alert('Your Device is now Online');
    })
  );
  useOnWindow(
    'offline',
    $(() => {
      alert('Your Device is now Offline');
    })
  );
  return <div></div>;
});

export default component$(() => {
  return <Online />;
});

useOnDocument

useOnDocument hook will add a event listener to document.

import { component$, useSignal, $, useOnDocument } from '@builder.io/qwik';

export const KeyBoard = component$(() => {
  const keyPressed = useSignal('');
  useOnDocument(
    'keydown',
    $((event) => {
      keyPressed.value = keyPressed.value + event.key;
    })
  );
  return <div>{keyPressed.value}</div>;
});

export default component$(() => {
  return <KeyBoard />;
});
Made with ❤️ by

© 2023 Builder.io, Inc.