Skip to main content

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)required

Function to execute after render. Can return a cleanup function.

dependenciesany[]

Optional array of dependencies. Effect runs when dependencies change.

Returns

void

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 - One-time Effect
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

Basic Usage​

Execute code after the component renders:

Simple Flash Effect
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 In 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>
);
});

DOM Measurements​

Measure DOM elements after they're rendered:

Auto-sizing Text Area
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:

Auto Focus Input
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:

Toast Notification
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:

Cleanup with useFlash
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