# Lifecycle Callbacks & Component Communication

In this guide, we'll explore how to build truly framework-agnostic UI components that work anywhere—React, Angular, Vue, or even with no framework at all! We'll deep dive into two critical aspects that separate amateur from professional implementations:

1. **Lifecycle callbacks** - The secret sauce that brings your components to life
    
2. **Component communication** - Making your components talk to each other seamlessly
    

Ready to future-proof your front-end skills? Let's dive in!

---

## Lifecycle Callbacks: The Heartbeat of Your Components

Imagine your Web Component as a living entity with a clear lifecycle: it's born, grows, changes, and eventually leaves the page. Each of these stages triggers specific callback methods that you can tap into:

### 1\. `connectedCallback()`: The Birthday Party

When your component joins the DOM, it's time to celebrate! This is where initialization happens:

```javascript
class BirthdayComponent extends HTMLElement {
  connectedCallback() {
    console.log('🎉 I\'m alive! Time to party!');
    this.innerHTML = `
      <div class="birthday-card">
        <h2>Hello World!</h2>
        <p>I was born at ${new Date().toLocaleTimeString()}</p>
      </div>
    `;
    // Start any necessary timers, fetch data, etc.
  }
}

customElements.define('birthday-card', BirthdayComponent);
```

**Try it yourself:** Add `<birthday-card></birthday-card>` to your HTML and watch it come to life in the browser console!

%[https://codepen.io/mnichols08/pen/KwKeGbQ] 

### 2\. `disconnectedCallback()`: The Farewell Tour

All good things come to an end. When your component is removed from the DOM, use this opportunity for a proper cleanup:

```javascript
class CleanupComponent extends HTMLElement {
  connectedCallback() {
    this.intervalId = setInterval(() => {
      console.log('Still here...');
    }, 1000);
  }
  
  disconnectedCallback() {
    console.log('💫 It\'s been a pleasure serving you! Cleaning up...');
    clearInterval(this.intervalId);
    // Remove event listeners, close connections, etc.
  }
}

customElements.define('cleanup-demo', CleanupComponent);
```

%[https://codepen.io/mnichols08/pen/OPJEaPg] **Hands-on challenge:** Create a component with a button that removes itself after a countdown. Watch the disconnectedCallback fire in your console!

### 3\. `attributeChangedCallback(name, oldValue, newValue)`: The Chameleon Effect

Components need to react to changes. This callback is your component's sense organ:

```javascript
class MoodRing extends HTMLElement {
  static get observedAttributes() {
    return ['mood'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'mood') {
      console.log(`🔄 Mood changing from ${oldValue || 'unset'} to ${newValue}!`);
      
      // Update the UI based on the new mood
      const moodEmojis = {
        happy: '😀',
        sad: '😢',
        angry: '😠',
        confused: '🤔'
      };
      
      this.innerHTML = `
        <div class="mood-display">
          <span style="font-size: 3rem">${moodEmojis[newValue] || '😐'}</span>
          <p>I'm feeling ${newValue || 'neutral'}!</p>
        </div>
      `;
    }
  }
}

customElements.define('mood-ring', MoodRing);
```

**Interactive example:** Click the buttons below to change my mood!

%[https://codepen.io/mnichols08/pen/dPyKQOX]

```xml
<mood-ring mood="happy"></mood-ring>
<div>
  <button onclick="document.querySelector('mood-ring').setAttribute('mood', 'happy')">Happy</button>
  <button onclick="document.querySelector('mood-ring').setAttribute('mood', 'sad')">Sad</button>
  <button onclick="document.querySelector('mood-ring').setAttribute('mood', 'angry')">Angry</button>
  <button onclick="document.querySelector('mood-ring').setAttribute('mood', 'confused')">Confused</button>
</div>
```

### 4\. `adoptedCallback()`: The Adoption Papers

This one's a bit like finding yourself in a new family. When your element moves to a new document:

```javascript
class AdoptedPet extends HTMLElement {
  adoptedCallback() {
    console.log('🏠 New home! Adjusting to my new document...');
    // Reset state for the new document context
  }
}

customElements.define('adopted-pet', AdoptedPet);
```

**Fun fact:** While less commonly used, this callback is crucial for applications that work with iframes or multiple documents!

%[https://codepen.io/mnichols08/pen/ogNyQeL] 

## Web Components in the Wild

Before we dive deeper, let's see who's already embracing this technology:

* **Google** uses Web Components across many of their products including YouTube and Google Maps
    
* **Microsoft** has embraced them for their Fluent UI design system
    
* **Adobe** leverages them in their Spectrum design system
    
* **Even Salesforce** built their Lightning Web Components on this technology
    

Why are these tech giants investing in Web Components? Because they're **future-proof**, **performant**, and **framework-agnostic**!

---

## Component Communication: Let's Talk!

Web Components are fantastic at encapsulation, but isolation isn't always desirable. Here's how to make them communicate effectively:

### 1\. Parent-Child Communication: The Family Chat

Just like in human families, parent and child components can have rich communication:

#### Parents → Children: Passing Down Wisdom

Parents can pass data to children through attributes or properties:

```javascript
class ParentComponent extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div class="parent">
        <h2>I'm the parent</h2>
        <child-component message="Be good!"></child-component>
        <button id="change-message">Change Message</button>
      </div>
    `;
    
    this.querySelector('#change-message').addEventListener('click', () => {
      const child = this.querySelector('child-component');
      child.setAttribute('message', 'Clean your room!');
    });
  }
}

class ChildComponent extends HTMLElement {
  static get observedAttributes() {
    return ['message'];
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'message') {
      this.querySelector('.message').textContent = newValue;
    }
  }
  
  connectedCallback() {
    this.innerHTML = `
      <div class="child" style="border: 1px solid blue; padding: 10px; margin: 10px 0;">
        <h3>I'm the child</h3>
        <p>Parent says: <span class="message">${this.getAttribute('message') || 'Nothing yet'}</span></p>
        <button class="respond">Respond to Parent</button>
      </div>
    `;
    
    this.querySelector('.respond').addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('child-response', {
        bubbles: true,
        detail: { response: 'Okay, I will!' }
      }));
    });
  }
}

customElements.define('parent-component', ParentComponent);
customElements.define('child-component', ChildComponent);
```

#### Children → Parents: Speaking Up

Children talk back to parents using custom events:

```javascript
// In the ParentComponent class
connectedCallback() {
  // ... previous code ...
  
  this.addEventListener('child-response', (e) => {
    alert(`Child says: ${e.detail.response}`);
  });
}
```

**Try it live:** Watch the following demo to see this parent-child communication in action!

%[https://codepen.io/mnichols08/pen/azbKQxZ] 

### 2\. Sibling Communication: The Playground Talk

Sometimes components at the same level need to coordinate:

```javascript
class SiblingContainer extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div class="container">
        <h2>Sibling Communication</h2>
        <sibling-sender></sibling-sender>
        <sibling-receiver></sibling-receiver>
      </div>
    `;
    
    // The parent mediates communication between siblings
    this.addEventListener('message-sent', (e) => {
      const receiver = this.querySelector('sibling-receiver');
      receiver.receiveMessage(e.detail.message);
    });
  }
}

class SiblingSender extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div style="border: 1px solid green; padding: 10px; margin: 10px 0;">
        <h3>Sibling A (Sender)</h3>
        <input type="text" placeholder="Type a message" value="Hello sibling!">
        <button>Send to Sibling B</button>
      </div>
    `;
    
    this.querySelector('button').addEventListener('click', () => {
      const message = this.querySelector('input').value;
      this.dispatchEvent(new CustomEvent('message-sent', {
        bubbles: true,
        detail: { message }
      }));
    });
  }
}

class SiblingReceiver extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div style="border: 1px solid purple; padding: 10px; margin: 10px 0;">
        <h3>Sibling B (Receiver)</h3>
        <p>Message received: <span class="message">Nothing yet</span></p>
      </div>
    `;
  }
  
  receiveMessage(message) {
    this.querySelector('.message').textContent = message;
    // Add animation to show the message arrived
    const messageElement = this.querySelector('.message');
    messageElement.style.animation = 'highlight 1s';
    setTimeout(() => { messageElement.style.animation = ''; }, 1000);
  }
}

customElements.define('sibling-container', SiblingContainer);
customElements.define('sibling-sender', SiblingSender);
customElements.define('sibling-receiver', SiblingReceiver);
```

%[https://codepen.io/mnichols08/pen/vEYrvYd]

### 3\. Global Event Bus: The Town Square

For components that are far apart in the DOM tree, a global event bus works like a town square:

```javascript
// EventBus.js - The town square where everyone meets
const EventBus = new EventTarget();

// Export it so components can import and use it
export { EventBus };
```

Using the event bus in components:

```javascript
import { EventBus } from './EventBus.js';

class GlobalSender extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div style="border: 1px solid red; padding: 10px;">
        <h3>Global Announcer</h3>
        <button>Broadcast Announcement</button>
      </div>
    `;
    
    this.querySelector('button').addEventListener('click', () => {
      EventBus.dispatchEvent(new CustomEvent('global-announcement', {
        detail: { message: 'Attention everyone! Important announcement!' }
      }));
    });
  }
}

class GlobalListener extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div style="border: 1px solid blue; padding: 10px;">
        <h3>Global Listener</h3>
        <p>Last announcement: <span class="announcement">None yet</span></p>
      </div>
    `;
    
    // Subscribe to global events
    EventBus.addEventListener('global-announcement', this.handleAnnouncement.bind(this));
  }
  
  disconnectedCallback() {
    // Clean up subscription when element is removed
    EventBus.removeEventListener('global-announcement', this.handleAnnouncement.bind(this));
  }
  
  handleAnnouncement(event) {
    this.querySelector('.announcement').textContent = event.detail.message;
  }
}

customElements.define('global-sender', GlobalSender);
customElements.define('global-listener', GlobalListener);
```

%[https://codepen.io/mnichols08/pen/NPWzeqm] **Pro tip:** This pattern works great for notifications, theme changes, or user authentication status that many components need to know about!

---

Each action demonstrates a different aspect of Web Components communication!

Check out the live demo below, and explore the code to see how it implements the concepts we've learned.

## Common Pitfalls to Avoid

1. **Forgetting to clean up resources** in `disconnectedCallback()` - This is the #1 cause of memory leaks!
    
2. **Not using Shadow DOM** when appropriate - Without it, your CSS might leak out or be affected by the parent page
    
3. **Overusing attributes** for complex data - Consider properties instead for objects and arrays
    
4. **Ignoring browser compatibility** - Always check [Can I Use](https://caniuse.com/#feat=custom-elementsv1) and consider polyfills for older browsers
    
5. **Creating too many small components** - Every component has overhead; find the right balance for your application
    

## Expert Tips

* **Debug attribute changes** by adding a console.log in attributeChangedCallback
    
* **Create a base component class** that handles common functionality for all your components
    
* **Document your components** with clear API comments - your future self will thank you!
    
* **Use Shadow DOM** for true encapsulation by adding `this.attachShadow({mode: 'open'})` in your constructor
    
* **Leverage custom events** for component communication rather than directly accessing other components
    

## Try It Yourself: Interactive Playground

Want to experiment without setting up a project? Copy and paste this code into [CodePen](https://codepen.io) or [JSFiddle](https://jsfiddle.net) to see Web Components in action immediately:

```javascript
class ColorToggler extends HTMLElement {
  constructor() {
    super();
    this.colors = ['#e91e63', '#2196f3', '#4caf50', '#ff9800'];
    this.currentIndex = 0;
  }
  
  connectedCallback() {
    this.style.display = 'block';
    this.style.padding = '20px';
    this.style.margin = '20px';
    this.style.backgroundColor = this.colors[0];
    this.style.color = 'white';
    this.style.borderRadius = '8px';
    this.style.cursor = 'pointer';
    this.style.transition = 'background-color 0.3s';
    this.style.userSelect = 'none';
    
    this.innerHTML = '<h3>Click me to change color!</h3>';
    this.addEventListener('click', this.cycleColor.bind(this));
  }
  
  cycleColor() {
    this.currentIndex = (this.currentIndex + 1) % this.colors.length;
    this.style.backgroundColor = this.colors[this.currentIndex];
  }
  
  disconnectedCallback() {
    this.removeEventListener('click', this.cycleColor);
  }
}

customElements.define('color-toggler', ColorToggler);

// Add to your HTML: <color-toggler></color-toggler>
```

%[https://codepen.io/mnichols08/pen/wBvXRKN] **Challenge:** Can you modify the component to display the current color name when clicked? (Solution at the bottom of this article)

---

**Challenge Solution**

```javascript
class ColorToggler extends HTMLElement {
  constructor() {
    super();
    this.colors = [
      { hex: '#e91e63', name: 'Pink' },
      { hex: '#2196f3', name: 'Blue' },
      { hex: '#4caf50', name: 'Green' },
      { hex: '#ff9800', name: 'Orange' }
    ];
    this.currentIndex = 0;
  }
  
  connectedCallback() {
    this.style.display = 'block';
    this.style.padding = '20px';
    this.style.margin = '20px';
    this.style.backgroundColor = this.colors[0].hex;
    this.style.color = 'white';
    this.style.borderRadius = '8px';
    this.style.cursor = 'pointer';
    this.style.transition = 'background-color 0.3s';
    this.style.userSelect = 'none';    
    this.innerHTML = `<h3>Click me! (Current: ${this.colors[0].name})</h3>`;
    this.addEventListener('click', this.cycleColor.bind(this));
  }
  
  cycleColor() {
    this.currentIndex = (this.currentIndex + 1) % this.colors.length;
    const currentColor = this.colors[this.currentIndex];
    this.style.backgroundColor = currentColor.hex;
    this.innerHTML = `<h3>Click me! (Current: ${currentColor.name})</h3>`;
  }
  
	  disconnectedCallback() {
    this.removeEventListener('click', this.cycleColor);
  }
}

customElements.define('color-toggler', ColorToggler);
```

%[https://codepen.io/mnichols08/pen/LEYrMZz] 

---

## Additional Resources:

* [MDN Web Components Guide](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
    
* [GitHub Repo for this Demo](https://github.com/mnichols08/WebComponentCallbacks)
    
* [Demo of Web Components Discussed Here](https://mnichols08.github.io/WebComponentCallbacks/)
