Documentation Index
Fetch the complete documentation index at: https://mintlify.com/preactjs/preact/llms.txt
Use this file to discover all available pages before exploring further.
The act function wraps test code that renders or updates components, ensuring all effects and renders are flushed before continuing.
Signature
function act(callback: () => void | Promise<void>): Promise<void>
callback
() => void | Promise<void>
required
The test code to run. Can be synchronous or return a Promise.
Returns a Promise that resolves when all effects and renders have been flushed.
How it works
From test-utils/src/index.js:27-112:
- Calls
setupRerender() to enable synchronous rendering
- Overrides
options.requestAnimationFrame to capture effect callbacks
- Executes your callback
- Flushes all pending renders
- Flushes all pending effects
- Repeats until no more work is pending
- Calls
teardown() to restore options
The function supports nested calls and both sync and async callbacks.
Usage
Basic usage
import { render } from 'preact';
import { act } from 'preact/test-utils';
test('renders component', async () => {
const container = document.createElement('div');
await act(() => {
render(<MyComponent />, container);
});
expect(container.textContent).toBe('Hello World');
});
Testing state updates
import { useState } from 'preact/hooks';
import { render } from 'preact';
import { act } from 'preact/test-utils';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
test('updates on click', async () => {
const container = document.createElement('div');
await act(() => {
render(<Counter />, container);
});
expect(container.querySelector('span').textContent).toBe('0');
await act(() => {
container.querySelector('button').click();
});
expect(container.querySelector('span').textContent).toBe('1');
});
Testing effects
import { useEffect, useState } from 'preact/hooks';
import { render } from 'preact';
import { act } from 'preact/test-utils';
function DataLoader({ url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url).then(r => r.json()).then(setData);
}, [url]);
return <div>{data ? data.title : 'Loading...'}</div>;
}
test('loads data', async () => {
const container = document.createElement('div');
// Mock fetch
global.fetch = () => Promise.resolve({
json: () => Promise.resolve({ title: 'Test' })
});
await act(async () => {
render(<DataLoader url="/api/data" />, container);
await new Promise(r => setTimeout(r, 0));
});
expect(container.textContent).toBe('Test');
});
Async callbacks
test('handles async updates', async () => {
const container = document.createElement('div');
await act(async () => {
render(<MyComponent />, container);
await someAsyncOperation();
// More updates...
});
// All updates are flushed
expect(container.textContent).toBe('Updated');
});
Implementation details
From test-utils/src/index.js:27-112:
export function act(cb) {
if (++actDepth > 1) {
// Nested calls: just execute callback
try {
const result = cb();
if (isThenable(result)) {
return result.then(
() => { --actDepth; },
e => { --actDepth; throw e; }
);
}
} catch (e) {
--actDepth;
throw e;
}
--actDepth;
return Promise.resolve();
}
const previousRequestAnimationFrame = options.requestAnimationFrame;
const rerender = setupRerender();
let flushes = [];
// Capture effect callbacks
options.requestAnimationFrame = fc => flushes.push(fc);
const finish = () => {
try {
rerender();
while (flushes.length) {
let toFlush = flushes;
flushes = [];
toFlush.forEach(x => x());
rerender();
}
} finally {
teardown();
}
options.requestAnimationFrame = previousRequestAnimationFrame;
--actDepth;
};
let result = cb();
if (isThenable(result)) {
return result.then(finish);
}
finish();
return Promise.resolve();
}
The function uses a depth counter to handle nested act() calls. Only the outermost call performs flushing.
Best practices
Always await act
// ✅ Good
await act(() => {
render(<Component />, container);
});
// ❌ Bad - effects may not be flushed
act(() => {
render(<Component />, container);
});
Wrap all renders and updates
// ✅ Good
await act(() => {
render(<Component />, container);
});
await act(() => {
button.click();
});
// ❌ Bad - update may not flush
await act(() => {
render(<Component />, container);
});
button.click(); // Not wrapped!
Use with testing libraries
import { render, fireEvent } from '@testing-library/preact';
// @testing-library/preact wraps act automatically