reactive-state-expert

majiayu000's avatarfrom majiayu000

Expert on FLUI's reactive state management with signals, hooks, and effects. Use when discussing state, signals, use_signal, use_effect, reactivity, or data flow.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 5, 2026

When & Why to Use This Skill

This Claude skill serves as a specialized expert for FLUI's reactive state management system, focusing on signals, hooks, and efficient data flow. It provides deep technical guidance on implementing responsive UI components, mastering reactivity patterns, and adhering to critical architectural rules in Rust-based UI development. By offering best practices for state lifting, memoization, and debugging, it helps developers build performant and maintainable applications while avoiding common pitfalls like infinite rebuilds or stale closures.

Use Cases

  • Architecting complex data flow patterns such as lifting state and managing derived state in FLUI-based applications.
  • Implementing reactive UI components using signals and hooks (use_signal, use_effect, use_memo) for real-time state synchronization.
  • Debugging and resolving reactivity issues, including infinite rebuild loops, stale closure captures, and missing state updates.
  • Optimizing application performance through strategic use of memoization, resource hooks for async data, and batching updates.
  • Ensuring code reliability by enforcing critical hook rules, such as maintaining consistent hook order and avoiding hooks within loops.
namereactive-state-expert
descriptionExpert on FLUI's reactive state management with signals, hooks, and effects. Use when discussing state, signals, use_signal, use_effect, reactivity, or data flow.

Reactive State Expert

Expert skill for FLUI's reactive state management system.

When to Use

Activate this skill when the user:

  • Works with signals or reactive state
  • Uses hooks like use_signal, use_effect, use_memo
  • Discusses data flow or state updates
  • Debugs reactivity issues
  • Implements stateful widgets

Core Concepts

Signals

Copy-based reactive primitives with automatic dependency tracking.

// Create signal
let count = use_signal(ctx, 0);

// Read value
let current = count.get();

// Update value (triggers rebuild)
count.set(current + 1);

// Update with closure
count.update(|x| x + 1);

Signal Internals

// Signals use Copy semantics
pub struct Signal<T: Copy> {
    id: SignalId,
    // Value stored in global signal store (DashMap)
}

// Thread-safe with DashMap for lock-free access

Available Hooks

use_signal

// Basic state
let count = use_signal(ctx, 0);

// Computed from props
let doubled = use_signal(ctx, self.value * 2);

use_effect

// Side effects that run after build
use_effect(ctx, || {
    tracing::info!("Component mounted");
    
    // Cleanup function (optional)
    || {
        tracing::info!("Component unmounted");
    }
});

// With dependencies
use_effect_with_deps(ctx, (count.get(),), |deps| {
    tracing::info!("Count changed to: {}", deps.0);
    || {}
});

use_memo

// Expensive computation cached
let expensive = use_memo(ctx, || {
    compute_expensive_value()
});

// With dependencies
let result = use_memo_with_deps(ctx, (a, b), |(a, b)| {
    expensive_computation(*a, *b)
});

use_ref

// Mutable reference that doesn't trigger rebuilds
let dom_ref = use_ref(ctx, || None::<DomHandle>);

Critical Rules

Hook Order

// ALWAYS call hooks in the same order!

// BAD - conditional hook
if condition {
    let signal = use_signal(ctx, 0);  // PANIC!
}

// GOOD - always call, conditionally use
let signal = use_signal(ctx, 0);
if condition {
    signal.set(42);
}

No Loops

// BAD - variable hook count
for i in 0..items.len() {
    let signal = use_signal(ctx, i);  // PANIC!
}

// GOOD - use single signal with collection
let items = use_signal(ctx, vec![1, 2, 3]);

BuildContext is Read-Only

// Signal handles rebuild scheduling internally
let signal = use_signal(ctx, 0);
signal.set(42);  // Triggers rebuild via callback

// DON'T try to schedule rebuilds manually during build

State Management Patterns

Lifting State

// Parent owns state
struct Parent;
impl View for Parent {
    fn build(&self, ctx: &mut BuildContext) -> impl IntoElement {
        let count = use_signal(ctx, 0);
        
        Column::new(vec![
            Display { count: count.get() },
            Controls { on_increment: move || count.update(|x| x + 1) },
        ])
    }
}

Derived State

impl View for MyWidget {
    fn build(&self, ctx: &mut BuildContext) -> impl IntoElement {
        let count = use_signal(ctx, 0);
        
        // Derived computation in build - recalculated each time
        let is_even = count.get() % 2 == 0;
        
        // Or use memo for expensive derivations
        let expensive = use_memo(ctx, || {
            compute_from_count(count.get())
        });
    }
}

Async State

// Resource hook for async data
let users = use_resource(ctx, || async {
    fetch_users().await
});

match users.get() {
    ResourceState::Loading => LoadingSpinner {},
    ResourceState::Ready(data) => UserList { users: data },
    ResourceState::Error(e) => ErrorDisplay { error: e },
}

Debugging Reactivity

Tracing Updates

#[tracing::instrument]
fn build(&self, ctx: &mut BuildContext) -> impl IntoElement {
    let count = use_signal(ctx, 0);
    tracing::debug!(count = count.get(), "Building with count");
    // ...
}

Common Issues

Issue: Infinite rebuilds

// BAD - sets signal during build
let signal = use_signal(ctx, 0);
signal.set(signal.get() + 1);  // Infinite loop!

// GOOD - set in event handler
Button::new("Increment")
    .on_press(move || signal.update(|x| x + 1))

Issue: Stale closures

// BAD - closure captures old value
let handler = || {
    let current = count.get();  // Captured at build time
    process(current);
};

// GOOD - read inside closure
let handler = move || {
    process(count.get());  // Fresh value
};

Issue: Missing updates

// Signals use Copy - modifications don't propagate
let mut value = signal.get();
value += 1;  // Only modifies local copy!

// GOOD - use set/update
signal.set(signal.get() + 1);

Performance Tips

  1. Minimize signal scope: Keep signals as local as possible
  2. Use memo for expensive computations: Cache derived data
  3. Batch updates: Group related state changes
  4. Avoid deep nesting: Flatten state structure when possible