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 through branches

Dependencies are discovered when control flow reaches their reads.

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

Here metadata starts being observed when track becomes truthy. Touch it before the branch only when changes to metadata must rerun the computation while track is still falsy.

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.