useEffect
Perform side effects in function components.
Import
import { useEffect } from '@bedrock-core/ui';
Signature
function useEffect(effect: () => (() => void)| void, deps?: any[]): void
Parameters
effect
- Type:
() => (() => void) | void - Description: Function that contains the side effect logic. Can optionally return a cleanup function.
deps (optional)
- Type:
any[] - Description: Dependency array. Effect runs when dependencies change. Omit for every execution, empty array
[]for mount only.
Returns
void
Usage
import { system } from '@minecraft/server';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const runId = system.runInterval(() => {
setSeconds(s => s + 1);
}, 20); // Runs every 20 ticks (1 second)
// Cleanup function
return () => system.clearRun(runId);
}, []); // Empty deps = run once on mount
return <Text x={10} y={10}>{`Time: ${seconds}s`}</Text>;
}
Dependency Array Behavior
| Deps | When Effect Runs |
|---|---|
undefined | After every execution |
[] | Once after initial execution (mount) |
[a, b] | When a or b changes |
Examples
Run Once on Mount
function DataLoader() {
const [data, setData] = useState(null);
useEffect(() => {
console.log('Component mounted, loading data...');
// Load data logic here
setData('Loaded data');
}, []); // Empty array = run once
return <Text x={10} y={10} width={300} height={30}>{data || 'Loading...'}</Text>;
}
Run on State Change
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
if (query.length > 0) {
console.log(`Searching for: ${query}`);
// Perform search
setResults([`Result for ${query}`]);
}
}, [query]); // Runs when query changes
return (
<>
<Text x={10} y={10} width={300} height={30}>{`Search: ${query}`}</Text>
{results.map((result, i) => (
<Text key={i} x={10} y={40 + i * 30} width={300} height={30}>{result}</Text>
))}
</>
);
}
Cleanup Function
import { system } from '@minecraft/server';
function EventListener() {
useEffect(() => {
const runId = system.runTimeout(() => {
console.log('Timeout executed');
}, 100); // Execute after 100 ticks (~5 seconds)
// Cleanup: cancel timeout when component unmounts
return () => {
system.clearRun(runId);
};
}, []);
return <Text x={10} y={10} width={300} height={30}>Timeout scheduled...</Text>;
}
Multiple Effects
function MultiEffect() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Player');
// Effect 1: Log count changes
useEffect(() => {
console.log(`Count changed to: ${count}`);
}, [count]);
// Effect 2: Log name changes
useEffect(() => {
console.log(`Name changed to: ${name}`);
}, [name]);
// Effect 3: Run on every execution
useEffect(() => {
console.log('Component executed');
});
return (
<>
<Text x={10} y={10} width={200} height={30}>{`Count: ${count}`}</Text>
<Text x={10} y={40} width={200} height={30}>{`Name: ${name}`}</Text>
</>
);
}
Timer/Interval
import { system } from '@minecraft/server';
function Countdown() {
const [timeLeft, setTimeLeft] = useState(60);
useEffect(() => {
if (timeLeft <= 0) return;
const runId = system.runInterval(() => {
setTimeLeft(timeLeft - 1);
}, 20); // Runs every 20 ticks (1 second)
return () => system.clearRun(runId);
}, [timeLeft]);
return (
<Text x={10} y={10} width={200} height={30}>{`Time Left: ${timeLeft}s`}</Text>
);
}
Fetch Data on Mount
function PlayerStats() {
const [stats, setStats] = useState(null);
const [loading, setLoading] = useState(true);
const player = usePlayer();
useEffect(() => {
// Simulate async data fetch
setLoading(true);
setTimeout(() => {
setStats({
health: player.health,
level: player.level
});
setLoading(false);
}, 1000);
}, []); // Load once on mount
if (loading) {
return <Text x={10} y={10} width={300} height={30}>Loading stats...</Text>;
}
return (
<>
<Text x={10} y={10} width={300} height={30}>{`Health: ${stats?.health}`}</Text>
<Text x={10} y={40} width={300} height={30}>{`Level: ${stats?.level}`}</Text>
</>
);
}
Dependent Effects
function DependentEffects() {
const [userId, setUserId] = useState(1);
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
// Effect 1: Load user when userId changes
useEffect(() => {
console.log(`Loading user ${userId}...`);
// Fetch user data
setUser({ id: userId, name: `User ${userId}` });
}, [userId]);
// Effect 2: Load posts when user changes
useEffect(() => {
if (user) {
console.log(`Loading posts for ${user.name}...`);
// Fetch posts
setPosts([`Post 1 by ${user.name}`]);
}
}, [user]);
return (
<>
<Text x={10} y={10} width={300} height={30}>{user?.name || 'Loading...'}</Text>
{posts.map((post, i) => (
<Text key={i} x={10} y={40 + i * 30} width={300} height={30}>{post}</Text>
))}
</>
);
}
Best Practices
Cleanup Functions
Always clean up side effects to prevent memory leaks:
import { system } from '@minecraft/server';
// ✅ Good - cleanup interval
useEffect(() => {
const runId = system.runInterval(() => {}, 20);
return () => system.clearRun(runId);
}, []);
// ❌ Bad - no cleanup
useEffect(() => {
system.runInterval(() => {}, 20);
}, []);
Dependency Array
Include all values used inside the effect:
// ✅ Good - all dependencies listed
useEffect(() => {
console.log(count, name);
}, [count, name]);
// ❌ Bad - missing dependencies
useEffect(() => {
console.log(count, name);
}, []); // count and name should be in deps!
Avoid Infinite Loops
// ❌ Bad - infinite loop
useEffect(() => {
setCount(count + 1); // Updates state, triggers update, runs effect again...
}, [count]);
// ✅ Good - condition or empty deps
useEffect(() => {
if (count < 10) {
setCount(count + 1);
}
}, [count]);
Split Unrelated Effects
// ✅ Good - separate effects
useEffect(() => {
// Effect for feature A
}, [depA]);
useEffect(() => {
// Effect for feature B
}, [depB]);
// ❌ Bad - combined unrelated effects
useEffect(() => {
// Effect for feature A
// Effect for feature B
}, [depA, depB]);
Common Pitfalls
- Forgetting cleanup - Causes memory leaks with event listeners, timers
- Missing dependencies - Leads to stale closures and bugs
- Too many dependencies - Effect runs too often
- State updates in effects without conditions - Can cause infinite loops