No results found

Indexing

Indexing

Understanding the Need for Component Indexing

When building compound components with multiple instances of the same element type, we need to track the specific index of each item. This is crucial for features like:

Single vs. Multiple Component Instances

Single instance components (like tooltips) don't typically need indexing:

<Tooltip.Root>
 <Tooltip.Trigger>
    Trigger
 </Tooltip.Trigger>
 <Tooltip.Content>
   Content
 </Tooltip.Content>
</Tooltip.Root>

Multiple instance components (like carousels) require proper indexing:

<Carousel.Root>
    <Carousel.Slide /> {/* index 0 */}
    <Carousel.Slide /> {/* index 1 */}
    <Carousel.Slide /> {/* index 2 */}
</Carousel.Root>

The Challenge with Qwik v1

Asynchronous Execution vs. Predictable Indexing

In Qwik v1, the framework executes code that might be asynchronous without a built-in scheduler:

Here's an example demonstrating the problem:

The Workaround for Qwik v1

To solve this in v1, we use synchronous code execution via inline components:

Using Inline Components

Qwik provides inline components that:

Implementation Steps

  1. Create a base component:
const CarouselRootBase = component$((props) => {
    return (
        <Render fallback="div" {...props}>
          <Slot />
        </Render>
    )
})

export const CarouselRoot = withAsChild(CarouselRootBase)
  1. Use withAsChild for synchronous execution:

The withAsChild function returns an inline component with a callback for synchronous code:

const CarouselRootBase = component$((props) => {
    return (
        <Render fallback="div" {...props}>
          <Slot />
        </Render>
    )
})

export const CarouselRoot = withAsChild(CarouselRootBase, (props) => {
    // Synchronous code runs here
    console.log("I can do synchronous work here!");
    props.newProp = newValue;
    return props;
})

Creating a Reliable Indexing System

We use two helper utilities:

  1. In the Root component: Reset indexes at the start of each render cycle
export const CarouselRoot = withAsChild(CarouselRootBase, (props) => {
    resetIndexes("carousel")  // Tie indexes to render lifecycle
    return props;
})
  1. In each item component: Get the next sequential index
export const CarouselSlide = withAsChild(CarouselSlideBase, (props) => {
  const nextIndex = getNextIndex("carousel");
  props._index = nextIndex;  // Store index in props for component access
  return props;
});

This solution produces correct indexes in most cases:

Both helper functions expect a namespace, to prevent collision with other components.

Limitations of the v1 Solution

The v1 solution breaks when consumers wrap our component instances in their own components:

This happens because:

In v1 on the consumer side, make sure not to compose multiple instance components.

The Complete Solution in Qwik v2

In Qwik v2, the built-in scheduler solves these problems by allowing us to:

<Carousel.Root>
    <Carousel.Slide /> {/* index 0 */}
    <Carousel.Slide /> {/* index 1 */}
    <Carousel.Slide /> {/* index 2 */}
</Carousel.Root>

Key Takeaways