UIElement Docs Version 0.8.5

๐Ÿ“‹ Detailed Walkthrough

This guide provides an in-depth look at how state flows through your components, from initialization to mutation, and how to build efficient, reactive Web Components.

Accessing Sub-Elements within a Component

Accessing sub-elements within your `UIElement` component is essential for setting up event listeners, auto-effects, or custom effects.

this.self

A reference to the custom element itself, which acts as the component wrapper. Useful for setting attributes or managing the component as a whole.

this.first(selector)

Returns the first matching sub-element within the component using querySelector(). This is best for targeting unique elements that are expected to exist once within the component.

this.first('.count').map(setText('count'));

this.all(selector)

Returns all matching sub-elements within the component using querySelectorAll(). Use this for targeting groups of elements that require batch processing or multiple effects.

this.all('.item').map(toggleClass('active', 'isActive'));

Chaining Effects, Event Handlers & Array Methods

It is possible to chain multiple effects and event handlers on the same accessed element. Additionally, array methods like .filter(), .find(), and .every() can be used to further refine or manipulate the list of elements returned by this.all().

// Example chaining event handler and effect
		  this.first('button')
			.map(on('click', () => this.set('clicked', true)))
			.map(toggleClass('clicked', 'clicked'));
		  
		  // Example filtering elements
		  this.all('.item')
			.filter(item => item.target.textContent.includes('Important'))
			.map(toggleClass('highlight', 'highlighted'));

Ways State Enters a Component

State in a component can come from several sources, each with its own way of being initialized and managed.

Observed Attributes

Attributes declared in static observedAttributes are automatically parsed and converted into reactive signals using attributeMap.

import { asInteger } from 'ui-element';
		  
		  static observedAttributes = ['count'];
		  static attributeMap = { count: asInteger };

If you set an attribute on your custom element, it becomes a signal within the component.

<counter-component count="5"></counter-component>

Defaults from DOM in Auto-Effects

When an auto-effect like setText() is used, the content of the target DOM element is taken as the default value of the signal if it hasn't been set manually.

<hello-world>
			<p>Hello, <span class="greeting">World</span>!</p>
		  </hello-world>
this.first('.greeting').map(setText('name'));

In this case, the name signal defaults to "World" as it uses the initial content of .greeting.

Manually Set Signals

You can set signals manually using this.set(). This is necessary in the following cases:

// Setting a default state
		  this.set('count', 10);
		  
		  // Setting a derived state
		  this.set('isEven', () => this.get('count') % 2 === 0);

Context Consumers

State can also be provided to a component through context. This allows sharing state across components and will be covered in the "Advanced Topics" section.

Ways State is Mutated

Once signals are established, they can be mutated through user interactions or asynchronous operations.

Event Handlers

Event handlers are the primary way to mutate state based on user input. You can use .map() and on() to attach event listeners to elements within the component.

this.first('input').map(on('input', (event) => this.set('name', event.target.value)));

When an event occurs, the signal name is updated, triggering any bound effects.

Resolved Promises

Asynchronous data sources, such as fetch requests or other Promises, can be used to mutate signals once resolved.

fetch('/data')
			.then(response => response.json())
			.then(data => this.set('data', data));

Ways to React to State Changes

When state changes in a component, `UIElement` provides various mechanisms to react and update the DOM or perform other side effects.

Auto-Effects for DOM Updates

Auto-effects are declarative bindings between signals and the DOM, automatically updating elements when signals change.

// Using multiple auto-effects
		  this.first('.count').map(setText('count'));
		  this.all('.item').map(toggleClass('active', 'isActive'));
		  this.self.map(toggleAttribute('selected', 'isSelected'));

Custom Effects with effect()

For more complex or custom reactions to state changes, use the effect() method to define your own effect handlers.

this.effect(() => {
			const count = this.get('count');
			console.log('Count changed:', count);
		  });

Custom effects allow you to perform side effects such as logging, manipulating multiple DOM elements, or calling external APIs when state changes.

Be careful to avoid creating infinite loops by not setting signals within effect handlers unless managing derived states thoughtfully.

Conclusion & Next Steps

Understanding how state flows into, through, and out of your `UIElement` components is key to building efficient, reactive Web Components. Now that you've learned about data flow and reactivity, explore the next sections to master "Best Practices & Patterns" or delve deeper into "Advanced Topics."