rEFui Best Practices & Troubleshooting

This guide consolidates performance tips, reactive patterns, and common pitfalls. Prefer these patterns before reaching for custom memoization or heavy abstractions.

General Architecture

Mental model: retained rendering + signals

Renderer Instance Management

Create renderer instances once at your application's entry point (typically main.js or index.js). Avoid creating multiple renderer instances within components.

// ✅ Good: Create renderer once in main.js
import { createDOMRenderer } from 'refui/dom';
import { defaults } from 'refui/browser';

// Single renderer instance
export const renderer = createDOMRenderer(defaults);

// Use throughout your app
renderer.render(document.getElementById('app'), App);
// ❌ Avoid: Creating renderers in components
const MyComponent = () => {
	// Don't do this - creates unnecessary renderer instances
	const renderer = createDOMRenderer(defaults);

	return <div>Hello</div>;
};

Component Organization

Keep your components focused and reusable. When components grow large, consider breaking them into smaller, composable pieces.

// ✅ Good: Small, focused components
const UserName = ({ user }) => <span>{user.name}</span>;
const UserEmail = ({ user }) => <span>{user.email}</span>;

const UserCard = ({ user }) => (
	<div>
		<UserName user={user} />
		<UserEmail user={user} />
	</div>
);

Reactivity Patterns

Reactivity & object properties

Signals track reads/writes on the signal itself, not nested object mutations.

// ❌ Mutation won’t notify
tracks.value[0].sampleRate = 44100

// ✅ Preferred: mutate in place + trigger for GC-friendly updates
tracks.value[0].sampleRate = 44100
tracks.trigger() // re-run dependents without recreating the array

// ✅ Replace object
const next = [...tracks.value]
next[0] = { ...next[0], sampleRate: 44100 }
tracks.value = next

// ✅ Nested signals for frequent updates
track.sampleRate.value = 44100

Computed dependency tracking (early-return trap)

Read dependencies before branching so they’re tracked.

const info = computed(() => {
	const t = track.value
	const m = metadata.value
	if (!t) return 'Ready'
	return `${t.title} - ${m}`
})

derivedExtract with nullable sources

derivedExtract tolerates null/undefined sources. You can extract directly without a safe wrapper:

const current = signal<Track | null>(null)
const { title, sampleRate } = derivedExtract(current, 'title', 'sampleRate')

watch vs useEffect vs onDispose

Performance Optimization

Keeping updates local

Coalescing updates

Use createDefer / createSchedule with cancelable deferrers to coalesce expensive work during rapid input (e.g. search boxes or window resizing).

Component Design

Keeping JSX lean

Define complex computeds at the top of a component; keep the JSX return as a simple "view" of your reactive data.

const fileInfo = computed(() => /* ... */)
return <div>{fileInfo}</div>

Dependency Scope

Use onCondition to scope fan-out in large lists or complex conditional branches. This prevents every item in a list from re-evaluating when a single global flag changes.