name: fix-modus-component-event-issues description: Debug and fix common event handling problems with Modus web components
Fix Modus Component Event Issues
Debug and fix common event handling problems with Modus web components.
When to Use
Use this skill when:
- Events aren't firing on Modus components
- Components aren't responding to user interactions
- Event handlers aren't being called
- You suspect event listener setup issues
Common Issues and Solutions
Issue 1: Events Not Firing
Symptoms: Click handlers, change handlers, or other events don't fire.
Causes:
- Missing event listener setup
- Wrong event name
- Component ref not set up
- Handler not passed as prop
Solution:
// ✅ CORRECT: Proper event listener setup
useEffect(() => {
const component = componentRef.current;
if (!component) return;
const handleEvent = (event: Event) => {
onEvent?.(event as CustomEvent<EventDetailType>);
};
if (onEvent) {
component.addEventListener("eventName", handleEvent);
}
return () => {
if (onEvent) {
component.removeEventListener("eventName", handleEvent);
}
};
}, [onEvent]); // ✅ Include handler in dependencies
Checklist:
- Component ref is created:
const componentRef = useRef<HTMLModusWcComponentElement>(null) - Ref is passed to web component:
<ModusWcComponent ref={componentRef} /> - Event listener is added in
useEffect - Event name matches Modus documentation
- Handler is included in dependency array
- Cleanup function removes listener
Issue 2: Missing Cleanup
Symptoms: Memory leaks, events firing multiple times, component errors after unmount.
Solution:
useEffect(() => {
const component = componentRef.current;
if (!component) return;
const handleEvent = (event: Event) => {
onEvent?.(event as CustomEvent<EventDetailType>);
};
component.addEventListener("eventName", handleEvent);
// ✅ CRITICAL: Always return cleanup function
return () => {
component.removeEventListener("eventName", handleEvent);
};
}, [onEvent]);
Issue 3: Wrong Event Names
Symptoms: Events never fire, wrong event type received.
Common Event Names:
| Component | Event Name | Detail Type |
|---|---|---|
| Checkbox | inputChange |
InputEvent |
| TextInput | inputChange |
InputEvent |
| DropdownMenu | itemSelect |
{ value: string } |
| DropdownMenu | menuVisibilityChange |
{ isVisible: boolean } |
| Navbar | searchClick |
MouseEvent | KeyboardEvent |
| Accordion | expandedChange |
{ expanded: boolean; index: number } |
Solution: Check Modus documentation for exact event names. They're case-sensitive.
Issue 4: Missing Ref
Symptoms: Cannot read property 'addEventListener' of null, events don't attach.
Solution:
// ✅ CORRECT: Ref setup
const componentRef = useRef<HTMLModusWcComponentElement>(null);
useEffect(() => {
const component = componentRef.current;
if (!component) return; // ✅ Check before use
// Set up listeners
}, []);
return (
<ModusWcComponent ref={componentRef} /> // ✅ Pass ref
);
Issue 5: Handler Not in Dependencies
Symptoms: Stale closures, handlers use old values.
Solution:
useEffect(() => {
const component = componentRef.current;
if (!component) return;
const handleEvent = (event: Event) => {
onEvent?.(event as CustomEvent<EventDetailType>);
};
if (onEvent) {
component.addEventListener("eventName", handleEvent);
}
return () => {
if (onEvent) {
component.removeEventListener("eventName", handleEvent);
}
};
}, [onEvent]); // ✅ Include handler in dependencies
Issue 6: Wrong Event Target
Symptoms: Can't access component properties, wrong value extracted.
Solution:
// ✅ CORRECT: Cast to proper element type
const handleInputChange = (event: Event) => {
const customEvent = event as CustomEvent<InputEvent>;
const value = (customEvent.target as HTMLModusWcTextInputElement).value;
onInputChange?.(value);
};
// ❌ WRONG: Don't use event.detail for input values
const handleInputChange = (event: Event) => {
const value = event.detail; // Wrong for input components!
};
Issue 7: Multiple Event Listeners
Symptoms: Events fire multiple times, duplicate handlers.
Solution:
// ✅ CORRECT: Conditional attachment
if (onEvent) {
component.addEventListener("eventName", handleEvent);
}
// ✅ CORRECT: Remove in cleanup
return () => {
if (onEvent) {
component.removeEventListener("eventName", handleEvent);
}
};
Debugging Checklist
When events aren't working, check:
Ref Setup
-
useRefis created with correct type - Ref is passed to web component
- Ref is checked before use (
if (!component) return)
-
Event Listener Setup
- Listener is added in
useEffect - Event name matches Modus documentation
- Handler function is defined
- Handler is conditionally attached (if prop exists)
- Listener is added in
Cleanup
- Cleanup function is returned from
useEffect - Listener is removed in cleanup
- Same handler reference is used for add/remove
- Cleanup function is returned from
Dependencies
- Handler props are in dependency array
- No missing dependencies causing stale closures
Event Handling
- Event is cast to correct
CustomEventtype - Value is extracted correctly (
target.valuevsdetail) - Handler is called with correct parameters
- Event is cast to correct
Debugging Tools
Console Logging
useEffect(() => {
const component = componentRef.current;
if (!component) {
console.error("Component ref is null");
return;
}
console.log("Setting up event listener for:", component);
const handleEvent = (event: Event) => {
console.log("Event fired:", event);
console.log("Event type:", event.type);
console.log("Event target:", event.target);
onEvent?.(event as CustomEvent<EventDetailType>);
};
component.addEventListener("eventName", handleEvent);
console.log("Event listener attached");
return () => {
console.log("Cleaning up event listener");
component.removeEventListener("eventName", handleEvent);
};
}, [onEvent]);
React DevTools
- Check if component ref is set
- Verify component is mounted
- Check if handlers are defined
Browser DevTools
- Check Event Listeners panel for attached listeners
- Verify event names match
- Check for errors in console
Common Patterns to Verify
Pattern 1: Basic Event Setup
// ✅ Verify this pattern
const componentRef = useRef<HTMLModusWcComponentElement>(null);
useEffect(() => {
const component = componentRef.current;
if (!component) return;
const handleEvent = (event: Event) => {
onEvent?.(event as CustomEvent<EventDetailType>);
};
if (onEvent) {
component.addEventListener("eventName", handleEvent);
}
return () => {
if (onEvent) {
component.removeEventListener("eventName", handleEvent);
}
};
}, [onEvent]);
return <ModusWcComponent ref={componentRef} />;
Pattern 2: Multiple Events
// ✅ Verify all events are set up
useEffect(() => {
const component = componentRef.current;
if (!component) return;
const handleEvent1 = (event: Event) => {
onEvent1?.(event as CustomEvent<Type1>);
};
const handleEvent2 = (event: Event) => {
onEvent2?.(event as CustomEvent<Type2>);
};
if (onEvent1) component.addEventListener("event1", handleEvent1);
if (onEvent2) component.addEventListener("event2", handleEvent2);
return () => {
if (onEvent1) component.removeEventListener("event1", handleEvent1);
if (onEvent2) component.removeEventListener("event2", handleEvent2);
};
}, [onEvent1, onEvent2]); // ✅ All handlers in dependencies
Quick Fixes
Fix: Events Not Firing
- Check if ref is set:
console.log(componentRef.current) - Verify event name: Check Modus docs
- Add console.log in handler to verify it's called
- Check if handler prop is passed
Fix: Events Firing Multiple Times
- Check if cleanup is removing listeners
- Verify handler isn't recreated on each render
- Check dependency array includes handler
Fix: Wrong Values
- Verify event type casting
- Check if using
target.valuevsdetail - For checkboxes, verify value inversion is handled
Related Files
src/components/ModusCheckbox.tsx- Event handling examplesrc/components/ModusDropdownMenu.tsx- Multiple events examplesrc/components/ModusNavbar.tsx- Complex event handling.cursor/rules/modus-react-integration.mdc- Integration patterns