useFlash
useFlash lets you run code exactly once after your component renders. It's perfect for animations, DOM measurements, focus management, or any operation that needs to happen after the DOM is ready.
Think of it as a "fire and forget" effect - it runs once, does its job, and gets out of the way. Unlike useEffect, which can run multiple times when dependencies change, useFlash is designed for one-time operations.
Why useFlash?
Sometimes you need to do something immediately after a component appears on screen - animate an element in, focus an input, measure DOM dimensions, or trigger a notification. These are one-time operations that shouldn't repeat unless you specifically want them to.
Traditional approaches often use useEffect with empty dependencies, but useFlash makes the intent clearer and provides better timing guarantees.
lens.useFlash
Executes a one-time effect after component render.
Signature
lens.useFlash(effectFunction, dependencies?)
Parameters
effectFunction() => void | (() => void)requiredFunction to execute after render. Can return a cleanup function.
dependenciesany[]Optional array of dependencies. Effect runs when dependencies change.
Returns
No return value
Example
// Animate element after render
lens.useFlash(() => {
const element = document.querySelector('.fade-in');
element.style.opacity = '1';
});
Comparison with Traditional Approaches
Let's see how useFlash compares to other ways of handling one-time effects:
- React useEffect
- Refract useFlash
- Live Result
import React, { useState, useEffect } from 'react';
const FadeInMessage = ({ message }) => {
const [isVisible, setIsVisible] = useState(false);
// useEffect with empty dependencies for one-time effect
useEffect(() => {
// Need to use requestAnimationFrame for proper timing
requestAnimationFrame(() => {
setIsVisible(true);
});
}, []); // Empty dependencies - runs once
return (
<div
className={`message ${isVisible ? 'visible' : 'hidden'}`}
style={{
opacity: isVisible ? 1 : 0,
transition: 'opacity 0.3s ease'
}}
>
{message}
</div>
);
};
Issues with React useEffect:
- Intent isn't clear (could be mistaken for missing dependencies)
- Still runs on every mount, not truly "one-time"
- Need to remember requestAnimationFrame for DOM timing
- Easy to accidentally add dependencies
import { createComponent } from 'refract';
const FadeInMessage = createComponent(({ lens, message }) => {
const isVisible = lens.useRefraction(false);
// Clear intent: this runs once after render
lens.useFlash(() => {
requestAnimationFrame(() => {
isVisible.set(true);
});
});
return (
<div
className={`message ${isVisible.value ? 'visible' : 'hidden'}`}
style={{
opacity: isVisible.value ? 1 : 0,
transition: 'opacity 0.3s ease'
}}
>
{message}
</div>
);
});
Benefits of Refract useFlash:
- Clear intent - obviously a one-time effect
- Perfect timing - runs after DOM is ready
- No dependency confusion
- Automatic cleanup when component unmounts
- Can return cleanup functions
Welcome! This message faded in smoothly.
useFlash triggered the animation exactly once after render
Basic Usage
Execute code after the component renders:
const FadeInMessage = createComponent(({ lens, message }) => {
const isVisible = lens.useRefraction(false);
// Flash effect runs after render
lens.useFlash(() => {
// Use requestAnimationFrame to ensure DOM is ready
requestAnimationFrame(() => {
isVisible.set(true);
});
});
return (
<div
className={`message ${isVisible.value ? 'visible' : 'hidden'}`}
style={{
opacity: isVisible.value ? 1 : 0,
transition: 'opacity 0.3s ease'
}}
>
{message}
</div>
);
});
Animation Effects
Create smooth animations that trigger after component mount:
- Slide Animation
- Staggered Animation
const SlideInPanel = createComponent(({ lens, children }) => {
const isOpen = lens.useRefraction(false);
lens.useFlash(() => {
// Animate in after mount
requestAnimationFrame(() => {
isOpen.set(true);
});
});
return (
<div
className="panel"
style={{
transform: isOpen.value ? 'translateX(0)' : 'translateX(-100%)',
transition: 'transform 0.4s cubic-bezier(0.4, 0, 0.2, 1)'
}}
>
{children}
</div>
);
});
const AnimatedList = createComponent(({ lens, items }) => {
const visibleItems = lens.useRefraction(new Set());
lens.useFlash(() => {
// Stagger the animation of list items
items.forEach((item, index) => {
setTimeout(() => {
visibleItems.set(prev => new Set([...prev, item.id]));
}, index * 100);
});
});
return (
<ul className="animated-list">
{items.map((item, index) => (
<li
key={item.id}
style={{
opacity: visibleItems.value.has(item.id) ? 1 : 0,
transform: visibleItems.value.has(item.id)
? 'translateY(0)'
: 'translateY(20px)',
transition: 'all 0.3s ease',
transitionDelay: `${index * 50}ms`
}}
>
{item.name}
</li>
))}
</ul>
);
});
DOM Measurements
Measure DOM elements after they're rendered:
const AutoResizeTextarea = createComponent(({ lens, value, onChange }) => {
const textareaRef = lens.useRefraction(null);
// Resize textarea after content changes
lens.useFlash(() => {
if (textareaRef.value) {
const textarea = textareaRef.value;
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
}, [value]);
return (
<textarea
ref={(el) => textareaRef.set(el)}
value={value}
onChange={onChange}
style={{
resize: 'none',
overflow: 'hidden',
minHeight: '40px',
transition: 'height 0.2s ease'
}}
placeholder="Type your message..."
/>
);
});
Focus Management
Handle focus after component updates:
const SearchInput = createComponent(({ lens, onSearch }) => {
const inputRef = lens.useRefraction(null);
const query = lens.useRefraction('');
const isSearching = lens.useRefraction(false);
// Focus input after component mounts
lens.useFlash(() => {
if (inputRef.value) {
inputRef.value.focus();
}
});
// Focus input after search completes
lens.useFlash(() => {
if (!isSearching.value && inputRef.value) {
inputRef.value.focus();
}
}, [isSearching.value]);
const handleSearch = async () => {
if (!query.value.trim()) return;
isSearching.set(true);
try {
await onSearch(query.value);
} finally {
isSearching.set(false);
}
};
return (
<div className="search-input">
<input
ref={(el) => inputRef.set(el)}
type="text"
value={query.value}
onChange={(e) => query.set(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
placeholder="Search..."
disabled={isSearching.value}
/>
<button
onClick={handleSearch}
disabled={isSearching.value || !query.value.trim()}
>
{isSearching.value ? 'Searching...' : 'Search'}
</button>
</div>
);
});
Notification System
Create toast notifications with automatic cleanup:
const Toast = createComponent(({ lens, message, type = 'info', duration = 3000, onClose }) => {
const isVisible = lens.useRefraction(false);
const isExiting = lens.useRefraction(false);
lens.useFlash(() => {
// Animate in
requestAnimationFrame(() => {
isVisible.set(true);
});
// Auto-close after duration
const timer = setTimeout(() => {
isExiting.set(true);
// Wait for exit animation before calling onClose
setTimeout(() => {
onClose?.();
}, 300);
}, duration);
// Cleanup timer if component unmounts
return () => clearTimeout(timer);
});
const handleClose = () => {
isExiting.set(true);
setTimeout(() => {
onClose?.();
}, 300);
};
return (
<div
className={`toast toast-${type} ${
isVisible.value && !isExiting.value ? 'toast-visible' : 'toast-hidden'
}`}
style={{
transform: isVisible.value && !isExiting.value
? 'translateY(0)'
: 'translateY(-100%)',
opacity: isVisible.value && !isExiting.value ? 1 : 0,
transition: 'all 0.3s ease'
}}
>
<span>{message}</span>
<button onClick={handleClose} className="toast-close">
×
</button>
</div>
);
});
const ToastContainer = createComponent(({ lens }) => {
const toasts = lens.useRefraction([]);
const addToast = (message, type = 'info') => {
const id = Date.now();
const newToast = { id, message, type };
toasts.set([...toasts.value, newToast]);
};
const removeToast = (id) => {
toasts.set(toasts.value.filter(toast => toast.id !== id));
};
return (
<div className="toast-container">
{toasts.value.map(toast => (
<Toast
key={toast.id}
message={toast.message}
type={toast.type}
onClose={() => removeToast(toast.id)}
/>
))}
</div>
);
});
Cleanup Functions
useFlash can return cleanup functions for proper resource management:
const ParticleEffect = createComponent(({ lens, isActive }) => {
const canvasRef = lens.useRefraction(null);
lens.useFlash(() => {
if (!isActive || !canvasRef.value) return;
const canvas = canvasRef.value;
const ctx = canvas.getContext('2d');
let animationId;
const animate = () => {
// Draw particles
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ... particle animation logic
animationId = requestAnimationFrame(animate);
};
animate();
// Cleanup function
return () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
};
}, [isActive]);
return (
<canvas
ref={(el) => canvasRef.set(el)}
width={400}
height={300}
style={{ border: '1px solid #ccc' }}
/>
);
});
Best Practices
- Use for post-render effects: Perfect for animations and DOM manipulations
- Provide cleanup: Return cleanup functions to prevent memory leaks
- Use requestAnimationFrame: For smooth animations, wrap in requestAnimationFrame
- Handle dependencies: Include dependencies array when effect should re-run
- Keep effects focused: Each useFlash should handle one specific concern
- Avoid heavy computations: useFlash runs after render, keep it lightweight
Related APIs
- useEffect - Reactive side effects with lifecycle
- useRefraction - Managing reactive state
- Components - Component lifecycle
- Side Effects - Understanding side effects