Skip to main content

useFlash

The useFlash hook executes effects after the component has rendered and the DOM has been updated. It's specifically designed for operations that need to happen after the render cycle, such as animations, DOM measurements, and focus management.

Syntax

lens.useFlash(effect, dependencies?)

Parameters

effect

  • Type: () => void
  • Required: Yes
  • Description: Function that contains post-render logic. Unlike useEffect, flash effects cannot return cleanup functions.

dependencies

  • Type: any[] | undefined
  • Required: No
  • Description: Array of values that the flash effect depends on. Effect re-runs when dependencies change.

Return Value

Returns void. Flash effects do not support cleanup functions.

Basic Usage

DOM Manipulation

const DOMManipulation = createComponent(({ lens }) => {
const elementRef = lens.useRefraction(null);
const isHighlighted = lens.useRefraction(false);

// Flash effect runs after render
lens.useFlash(() => {
if (elementRef.value && isHighlighted.value) {
elementRef.value.style.backgroundColor = 'yellow';
elementRef.value.scrollIntoView({ behavior: 'smooth' });
}
}, [isHighlighted.value]);

return (
<div>
<button onClick={() => isHighlighted.set(!isHighlighted.value)}>
Toggle Highlight
</button>
<div ref={(el) => elementRef.set(el)}>
This element can be highlighted
</div>
</div>
);
});

Focus Management

const FocusManagement = createComponent(({ lens }) => {
const inputRef = lens.useRefraction(null);
const shouldFocus = lens.useRefraction(false);

lens.useFlash(() => {
if (inputRef.value && shouldFocus.value) {
inputRef.value.focus();
inputRef.value.select();
}
}, [shouldFocus.value]);

return (
<div>
<input
ref={(el) => inputRef.set(el)}
placeholder="This input can be auto-focused"
/>
<button onClick={() => shouldFocus.set(true)}>
Focus Input
</button>
</div>
);
});

Animation Examples

CSS Transitions

const CSSTransition = createComponent(({ lens }) => {
const elementRef = lens.useRefraction(null);
const isVisible = lens.useRefraction(false);

lens.useFlash(() => {
if (elementRef.value) {
if (isVisible.value) {
// Trigger enter animation
elementRef.value.style.opacity = '0';
elementRef.value.style.transform = 'translateY(20px)';

// Force reflow
elementRef.value.offsetHeight;

// Apply transition
elementRef.value.style.transition = 'all 0.3s ease';
elementRef.value.style.opacity = '1';
elementRef.value.style.transform = 'translateY(0)';
}
}
}, [isVisible.value]);

return (
<div>
<button onClick={() => isVisible.set(!isVisible.value)}>
Toggle Element
</button>
{isVisible.value && (
<div ref={(el) => elementRef.set(el)} className="animated-element">
Animated Content
</div>
)}
</div>
);
});

JavaScript Animations

const JSAnimation = createComponent(({ lens }) => {
const elementRef = lens.useRefraction(null);
const animationTrigger = lens.useRefraction(0);

lens.useFlash(() => {
if (elementRef.value && animationTrigger.value > 0) {
const element = elementRef.value;
const startTime = Date.now();
const duration = 500;

const animate = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);

// Easing function
const easeOut = 1 - Math.pow(1 - progress, 3);

// Apply animation
element.style.transform = `scale(${1 + easeOut * 0.2})`;
element.style.backgroundColor = `hsl(${progress * 360}, 70%, 50%)`;

if (progress < 1) {
requestAnimationFrame(animate);
} else {
// Reset styles
element.style.transform = 'scale(1)';
element.style.backgroundColor = '';
}
};

requestAnimationFrame(animate);
}
}, [animationTrigger.value]);

return (
<div>
<div ref={(el) => elementRef.set(el)} className="js-animated">
Click the button to animate me!
</div>
<button onClick={() => animationTrigger.set(prev => prev + 1)}>
Animate
</button>
</div>
);
});

DOM Measurements

Element Dimensions

const ElementMeasurement = createComponent(({ lens }) => {
const contentRef = lens.useRefraction(null);
const dimensions = lens.useRefraction({ width: 0, height: 0 });
const content = lens.useRefraction('Short text');

lens.useFlash(() => {
if (contentRef.value) {
const rect = contentRef.value.getBoundingClientRect();
dimensions.set({
width: rect.width,
height: rect.height
});
}
}, [content.value]);

const addMoreContent = () => {
content.set(prev => prev + ' More text added to change dimensions.');
};

return (
<div>
<div ref={(el) => contentRef.set(el)} style={{ border: '1px solid #ccc', padding: '10px' }}>
{content.value}
</div>
<p>Dimensions: {dimensions.value.width}px × {dimensions.value.height}px</p>
<button onClick={addMoreContent}>Add More Content</button>
</div>
);
});

Scroll Position

const ScrollPosition = createComponent(({ lens }) => {
const containerRef = lens.useRefraction(null);
const scrollInfo = lens.useRefraction({ top: 0, left: 0 });
const shouldScrollToBottom = lens.useRefraction(false);

lens.useFlash(() => {
if (containerRef.value) {
if (shouldScrollToBottom.value) {
containerRef.value.scrollTop = containerRef.value.scrollHeight;
shouldScrollToBottom.set(false);
}

// Update scroll position info
scrollInfo.set({
top: containerRef.value.scrollTop,
left: containerRef.value.scrollLeft
});
}
}, [shouldScrollToBottom.value]);

return (
<div>
<div
ref={(el) => containerRef.set(el)}
style={{ height: '200px', overflow: 'auto', border: '1px solid #ccc' }}
onScroll={() => {
if (containerRef.value) {
scrollInfo.set({
top: containerRef.value.scrollTop,
left: containerRef.value.scrollLeft
});
}
}}
>
{Array.from({ length: 50 }, (_, i) => (
<div key={i} style={{ padding: '10px' }}>
Item {i + 1}
</div>
))}
</div>
<p>Scroll Position: {scrollInfo.value.top}px from top</p>
<button onClick={() => shouldScrollToBottom.set(true)}>
Scroll to Bottom
</button>
</div>
);
});

Advanced Patterns

Conditional Flash Effects

const ConditionalFlash = createComponent(({ lens, isEnabled }) => {
const elementRef = lens.useRefraction(null);
const counter = lens.useRefraction(0);

lens.useFlash(() => {
// Only run flash effect when enabled
if (isEnabled && elementRef.value) {
elementRef.value.style.boxShadow = '0 0 10px rgba(0, 255, 0, 0.5)';

setTimeout(() => {
if (elementRef.value) {
elementRef.value.style.boxShadow = '';
}
}, 200);
}
}, [counter.value, isEnabled]);

return (
<div>
<div ref={(el) => elementRef.set(el)} style={{ padding: '20px', border: '1px solid #ccc' }}>
Flash effect {isEnabled ? 'enabled' : 'disabled'}
</div>
<button onClick={() => counter.set(prev => prev + 1)}>
Trigger Flash ({counter.value})
</button>
</div>
);
});

Multiple Element Coordination

const MultiElementFlash = createComponent(({ lens }) => {
const elementsRef = lens.useRefraction([]);
const animationTrigger = lens.useRefraction(false);

lens.useFlash(() => {
if (animationTrigger.value && elementsRef.value.length > 0) {
elementsRef.value.forEach((element, index) => {
if (element) {
setTimeout(() => {
element.style.transform = 'scale(1.1)';
element.style.transition = 'transform 0.2s ease';

setTimeout(() => {
element.style.transform = 'scale(1)';
}, 200);
}, index * 100);
}
});

animationTrigger.set(false);
}
}, [animationTrigger.value]);

const setElementRef = (index) => (el) => {
elementsRef.set(prev => {
const newRefs = [...prev];
newRefs[index] = el;
return newRefs;
});
};

return (
<div>
<div style={{ display: 'flex', gap: '10px', margin: '20px 0' }}>
{Array.from({ length: 5 }, (_, i) => (
<div
key={i}
ref={setElementRef(i)}
style={{
width: '50px',
height: '50px',
backgroundColor: '#007bff',
borderRadius: '4px'
}}
/>
))}
</div>
<button onClick={() => animationTrigger.set(true)}>
Animate All Elements
</button>
</div>
);
});

Integration with Third-Party Libraries

Chart Libraries

const ChartIntegration = createComponent(({ lens, data }) => {
const chartRef = lens.useRefraction(null);
const chartInstance = lens.useRefraction(null);

// Initialize chart after mount
lens.useFlash(() => {
if (chartRef.value && !chartInstance.value) {
// Initialize chart library (e.g., Chart.js)
const chart = new Chart(chartRef.value, {
type: 'line',
data: data,
options: {
responsive: true,
animation: {
duration: 1000
}
}
});

chartInstance.set(chart);
}
}, []);

// Update chart when data changes
lens.useFlash(() => {
if (chartInstance.value && data) {
chartInstance.value.data = data;
chartInstance.value.update('active');
}
}, [data]);

return (
<div>
<canvas ref={(el) => chartRef.set(el)} />
</div>
);
});

Animation Libraries

const AnimationLibrary = createComponent(({ lens }) => {
const elementRef = lens.useRefraction(null);
const shouldAnimate = lens.useRefraction(false);

lens.useFlash(() => {
if (elementRef.value && shouldAnimate.value) {
// Using GSAP or similar animation library
gsap.fromTo(elementRef.value,
{
opacity: 0,
y: 50
},
{
opacity: 1,
y: 0,
duration: 0.5,
ease: "power2.out"
}
);

shouldAnimate.set(false);
}
}, [shouldAnimate.value]);

return (
<div>
<div ref={(el) => elementRef.set(el)}>
Animated with GSAP
</div>
<button onClick={() => shouldAnimate.set(true)}>
Animate
</button>
</div>
);
});

Performance Considerations

Avoiding Expensive Operations

const PerformantFlash = createComponent(({ lens }) => {
const elementRef = lens.useRefraction(null);
const needsUpdate = lens.useRefraction(false);

lens.useFlash(() => {
if (needsUpdate.value && elementRef.value) {
// Use requestAnimationFrame for smooth animations
requestAnimationFrame(() => {
if (elementRef.value) {
// Batch DOM operations
elementRef.value.style.cssText = `
transform: translateX(100px);
opacity: 0.5;
transition: all 0.3s ease;
`;
}
});

needsUpdate.set(false);
}
}, [needsUpdate.value]);

return (
<div>
<div ref={(el) => elementRef.set(el)}>
Performant element
</div>
<button onClick={() => needsUpdate.set(true)}>
Update
</button>
</div>
);
});

Testing Flash Effects

Testing DOM Manipulation

import { render, act } from '@refract/testing-utils';

describe('Flash Effects', () => {
test('applies styles after render', async () => {
const TestComponent = createComponent(({ lens }) => {
const elementRef = lens.useRefraction(null);
const trigger = lens.useRefraction(false);

lens.useFlash(() => {
if (elementRef.value && trigger.value) {
elementRef.value.style.backgroundColor = 'red';
}
}, [trigger.value]);

return (
<div>
<div ref={(el) => elementRef.set(el)} data-testid="target">
Target Element
</div>
<button onClick={() => trigger.set(true)}>
Trigger
</button>
</div>
);
});

const { getByTestId, getByRole } = render(<TestComponent />);
const targetElement = getByTestId('target');

expect(targetElement.style.backgroundColor).toBe('');

act(() => {
fireEvent.click(getByRole('button'));
});

// Flash effects run after render
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});

expect(targetElement.style.backgroundColor).toBe('red');
});
});

Best Practices

1. Use for Post-Render Operations Only

// ✅ Good - DOM manipulation after render
lens.useFlash(() => {
if (elementRef.value) {
elementRef.value.focus();
}
}, [shouldFocus]);

// ❌ Bad - State updates (use useEffect instead)
lens.useFlash(() => {
setState(newValue); // This should be in useEffect
}, [trigger]);

2. Check Element Existence

// ✅ Good - Always check if element exists
lens.useFlash(() => {
if (elementRef.value) {
elementRef.value.style.color = 'red';
}
}, [trigger]);

// ❌ Bad - No null check
lens.useFlash(() => {
elementRef.value.style.color = 'red'; // May throw error
}, [trigger]);

3. Use Specific Dependencies

// ✅ Good - Specific dependencies
lens.useFlash(() => {
animateElement();
}, [animationTrigger]);

// ❌ Bad - Missing or too broad dependencies
lens.useFlash(() => {
animateElement();
}, []); // Missing dependency

4. Batch DOM Operations

// ✅ Good - Batch DOM operations
lens.useFlash(() => {
if (elementRef.value) {
const element = elementRef.value;
element.style.cssText = `
transform: scale(1.1);
opacity: 0.8;
transition: all 0.3s ease;
`;
}
}, [trigger]);

// ❌ Bad - Multiple style assignments
lens.useFlash(() => {
if (elementRef.value) {
elementRef.value.style.transform = 'scale(1.1)';
elementRef.value.style.opacity = '0.8';
elementRef.value.style.transition = 'all 0.3s ease';
}
}, [trigger]);