Reactive Communication in Qwik

george isaacgeorge isaac
3 min read

Introduction

Sometimes in a Qwik app, we want our components to communicate. Specifically, we might want a child component to notify its parent when something important happens inside it. This is a common need in developing web apps for example.

Let’s take a simple example:

Imagine we are building a form that asks the user for their age. If the user is 18 or older, we want to show a message that says, "You are an adult!" But the input field for the age lives in a child component. So, how do we let the parent know when the age is 18 or more?

We’ll walk through how to achieve this in Qwik using signals and useTask$.

To keep this tutorial short, we won’t go over the steps on how to install and set up Qwik. You check out the steps here https://qwik.dev/docs/getting-started/

The Goal

  • Child component has an age input.

  • Parent listens for updates and reacts when the age is 18 or above.

Step 1: Parent Component

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

export default component$(() => {
  const isAdult = useSignal(false);

  return (
    <div>
      <h2>Enter your age:</h2>
      <AgeInput
        onStatusChange$={$((status: boolean) => {
          isAdult.value = status;
        })}
      />
      {isAdult.value && <p>You are an adult!</p>}
    </div>
  );
});

Here, the parent:

  • Tracks if the user is an adult using isAdult signal.

  • Passes a callback function onStatusChange$ to the child.

Step 2: Child Component

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

export interface AgeInputProps {
  onStatusChange$: QRL<(isAdult: boolean) => void>;
}

export default component$<AgeInputProps>((props) => {
  const age = useSignal<number>(0);

  useTask$(({ track }) => {
    track(() => age.value);

    props.onStatusChange$(age.value >= 18);
  });

  return (
    <input
      type="number"
      value={age.value}
      onInput$={(e) => (age.value = parseInt((e.target as HTMLInputElement).value))}
    />
  );
});

In the child:

  • age is a reactive signal.

  • We use useTask$ to watch changes to age.value.

  • When the age updates, we call the parent's onStatusChange$ with a boolean indicating if the user is an adult.

Why Use useTask$ Here?

useTask$ allows the child to react to changes over time in a clean, reactive way. You might think, "Why not just call the function directly in onInput$?" That would work, but useTask$ gives us a few key benefits:

  • We keep logic separated from UI events.

  • We can easily track multiple values if needed in future.

  • Our code becomes more reactive and maintainable.

Summary

This pattern lets components collaborate in a reactive way:

RoleWhat It Does
ParentReceives updates from the child and decides what to do
ChildTracks some internal state (like age) and notifies the parent when it changes
useTask$Observes the internal state and triggers side effects

This example might be simple, but the pattern scales beautifully. Anytime you want a component to inform another about changes, this is a great tool to have in your belt.

0
Subscribe to my newsletter

Read articles from george isaac directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

george isaac
george isaac