Skip to main content

Overview

Hooks are special functions that let you tap into Freya’s reactivity and lifecycle system. They enable you to add state, side effects, and other features to your components without writing complex classes.
All hooks in Freya are prefixed with use_ - for example, use_state, use_effect, use_memo.

What Are Hooks?

Hooks are functions that let you “hook into” Freya’s component lifecycle and reactive features. They can only be called from within a component’s render method.
use freya::prelude::*;

#[derive(PartialEq)]
struct MyComponent;

impl Component for MyComponent {
    fn render(&self) -> impl IntoElement {
        // ✅ Hooks are called here, at the top of render
        let mut count = use_state(|| 0);
        let theme = use_theme();
        
        rect().child(format!("Count: {}", count.read()))
    }
}

Rules of Hooks

To ensure hooks work correctly, you must follow these rules:
Breaking these rules will cause runtime panics. The compiler cannot catch these mistakes, so be careful!

1. Only Call Hooks at the Top Level

Never call hooks inside conditions, loops, or nested functions:
#[derive(PartialEq)]
struct MyComponent(bool);

impl Component for MyComponent {
    fn render(&self) -> impl IntoElement {
        // ❌ Hook called conditionally
        if self.0 {
            let state = use_state(|| 5);
        }
        
        rect()
    }
}

2. Only Call Hooks in Render Methods

Don’t call hooks in event handlers or other callbacks:
#[derive(PartialEq)]
struct MyComponent;

impl Component for MyComponent {
    fn render(&self) -> impl IntoElement {
        rect().on_mouse_up(|_| {
            // ❌ Hook called in event handler
            let state = use_state(|| false);
        })
    }
}

3. Don’t Call Hooks in Loops

The number of hook calls must be consistent across renders:
#[derive(PartialEq)]
struct MyComponent;

impl Component for MyComponent {
    fn render(&self) -> impl IntoElement {
        // ❌ Hook called in loop
        for i in 0..5 {
            let state = use_state(|| i);
        }
        
        rect()
    }
}

Core Hooks

use_state

The most fundamental hook for managing local component state.
use freya::prelude::*;

#[derive(PartialEq)]
struct Counter;

impl Component for Counter {
    fn render(&self) -> impl IntoElement {
        let mut count = use_state(|| 0);

        rect()
            .child(format!("Count: {}", count.read()))
            .child(
                Button::new()
                    .on_press(move |_| *count.write() += 1)
                    .child("+")
            )
    }
}
The initialization function || 0 only runs once when the component first mounts. On subsequent renders, it returns the stored value.

use_effect

Run side effects after rendering:
use freya::prelude::*;

#[derive(PartialEq)]
struct Logger;

impl Component for Logger {
    fn render(&self) -> impl IntoElement {
        let mut count = use_state(|| 0);

        // Runs after every render where count changes
        use_effect(move || {
            println!("Count changed to: {}", count.read());
        });

        rect()
            .child(format!("Count: {}", count.read()))
            .child(
                Button::new()
                    .on_press(move |_| *count.write() += 1)
                    .child("+")
            )
    }
}
use_effect automatically tracks which state values you read inside it and only re-runs when those values change.

use_memo

Memoize expensive computations:
use freya::prelude::*;

#[derive(PartialEq)]
struct ExpensiveComponent;

impl Component for ExpensiveComponent {
    fn render(&self) -> impl IntoElement {
        let mut input = use_state(|| "test".to_string());

        // Only recomputes when input changes
        let processed = use_memo(move || {
            expensive_operation(input.read().as_str())
        });

        rect().child(processed.read().clone())
    }
}

fn expensive_operation(input: &str) -> String {
    // Simulate expensive work
    input.to_uppercase()
}

use_hook

The foundational hook used by all others. Store any value:
use std::rc::Rc;
use std::cell::RefCell;
use freya::prelude::*;

#[derive(PartialEq)]
struct CustomHookExample;

impl Component for CustomHookExample {
    fn render(&self) -> impl IntoElement {
        // Store a reference-counted value
        let cached_data = use_hook(|| {
            Rc::new(RefCell::new(Vec::new()))
        });

        rect()
    }
}

Lifecycle Hooks

use_effect - Mount and Unmount

Handle component lifecycle events:
use freya::prelude::*;

#[derive(PartialEq)]
struct LifecycleComponent;

impl Component for LifecycleComponent {
    fn render(&self) -> impl IntoElement {
        // Run once on mount
        use_effect(|| {
            println!("Component mounted!");
            
            // Return cleanup function for unmount
            move || {
                println!("Component unmounted!");
            }
        });

        rect().child("Hello")
    }
}

Async Effects with use_future_task

Run async operations:
use freya::prelude::*;

#[derive(PartialEq)]
struct DataFetcher;

impl Component for DataFetcher {
    fn render(&self) -> impl IntoElement {
        let mut data = use_state(|| None);

        use_future_task(move || async move {
            // Simulate API call
            tokio::time::sleep(std::time::Duration::from_secs(1)).await;
            data.set(Some("Data loaded!".to_string()));
        });

        if let Some(content) = data.read().as_ref() {
            rect().child(content.clone())
        } else {
            rect().child("Loading...")
        }
    }
}
Async effects run on every render by default. Use use_memo or dependencies to control when they run.

Common Hooks

Animation

use freya::prelude::*;

#[derive(PartialEq)]
struct AnimatedBox;

impl Component for AnimatedBox {
    fn render(&self) -> impl IntoElement {
        let mut animation = use_animation(|| {
            AnimNum::new(0., 200.)
                .duration(500.)
                .easing(Easing::EaseInOut)
        });

        use_effect(move || {
            animation.start();
        });

        rect()
            .width(Size::px(animation.value() as f32))
            .height(Size::px(100.))
            .background((255, 0, 0))
    }
}

Theming

use freya::prelude::*;

#[derive(PartialEq)]
struct ThemedComponent;

impl Component for ThemedComponent {
    fn render(&self) -> impl IntoElement {
        let theme = use_theme();

        rect()
            .background(theme.background)
            .color(theme.foreground)
            .child("Themed content")
    }
}

Focus Management

use freya::prelude::*;

#[derive(PartialEq)]
struct FocusableComponent;

impl Component for FocusableComponent {
    fn render(&self) -> impl IntoElement {
        let mut focus = use_focus();

        rect()
            .focusable(true)
            .on_focus(move |_| {
                println!("Focused!");
            })
            .child("Click to focus")
    }
}

Custom Hooks

You can create your own hooks by combining existing ones:
use freya::prelude::*;

// Custom hook for window size
fn use_window_size() -> (State<f64>, State<f64>) {
    let mut width = use_state(|| 800.0);
    let mut height = use_state(|| 600.0);

    use_effect(move || {
        // Listen for resize events
        // Update width and height
    });

    (width, height)
}

#[derive(PartialEq)]
struct ResponsiveComponent;

impl Component for ResponsiveComponent {
    fn render(&self) -> impl IntoElement {
        let (width, height) = use_window_size();

        rect().child(format!(
            "Window: {}x{}",
            width.read(),
            height.read()
        ))
    }
}
Custom hooks must follow the same rules as built-in hooks - they should start with use_ and only be called from within render methods.

Advanced Patterns

Dependency-Based Effects

Control when effects run:
use freya::prelude::*;

#[derive(PartialEq)]
struct DependentEffect;

impl Component for DependentEffect {
    fn render(&self) -> impl IntoElement {
        let mut count = use_state(|| 0);
        let mut other = use_state(|| 0);

        // Only runs when count changes
        use_effect(move || {
            let _ = count.read(); // Track count
            println!("Count changed!");
        });

        rect()
    }
}

Multiple State Updates

Batch multiple state updates:
use freya::prelude::*;

#[derive(PartialEq)]
struct BatchedUpdates;

impl Component for BatchedUpdates {
    fn render(&self) -> impl IntoElement {
        let mut count = use_state(|| 0);
        let mut name = use_state(|| "Alice".to_string());

        rect()
            .child(Button::new()
                .on_press(move |_| {
                    // Both updates happen together
                    *count.write() += 1;
                    name.set(format!("User {}", count.read()));
                })
                .child("Update")
            )
    }
}

Best Practices

Always call hooks at the very top of your render method:
fn render(&self) -> impl IntoElement {
    // ✅ All hooks first
    let state1 = use_state(|| 0);
    let state2 = use_state(|| false);
    let theme = use_theme();
    
    // Then build UI
    rect()
}
Freya automatically tracks what state you read. You don’t need to manually specify dependencies:
use_effect(move || {
    // Automatically tracks count
    println!("{}", count.read());
});
Use use_memo for any operation that takes significant time:
let filtered = use_memo(move || {
    items.read()
        .iter()
        .filter(|item| item.matches_search(query.read()))
        .collect::<Vec<_>>()
});
Return cleanup functions from effects when needed:
use_effect(|| {
    let timer = set_interval(|| { /* ... */ });
    
    move || {
        clear_interval(timer);
    }
});

Common Pitfalls

Infinite Loops

Be careful not to create infinite loops:
// ❌ BAD: Infinite loop
use_effect(move || {
    *count.write() += 1; // Triggers re-render, runs effect again
});

// ✅ GOOD: Only runs once
use_effect(move || {
    // No state mutations
    println!("Mounted!");
});

Stale Closures

State values are Copy, so you can freely move them into closures:
let count = use_state(|| 0);

// ✅ GOOD: count is Copy
Button::new()
    .on_press(move |_| *count.write() += 1)
    .child("+")

Complete Example

use freya::prelude::*;

fn main() {
    launch(LaunchConfig::new().with_window(WindowConfig::new(app)))
}

fn app() -> impl IntoElement {
    rect()
        .expanded()
        .center()
        .child(TodoApp {})
}

#[derive(PartialEq)]
struct TodoApp;

impl Component for TodoApp {
    fn render(&self) -> impl IntoElement {
        let mut todos = use_state(|| Vec::new());
        let mut input = use_state(|| String::new());

        // Log whenever todos change
        use_effect(move || {
            println!("Todo count: {}", todos.read().len());
        });

        rect()
            .width(Size::px(400.))
            .child(
                rect()
                    .horizontal()
                    .spacing(8.0)
                    .child(
                        Input::new(input.into_writable())
                            .placeholder("New todo...")
                    )
                    .child(
                        Button::new()
                            .on_press(move |_| {
                                let text = input.read().clone();
                                if !text.is_empty() {
                                    todos.write().push(text);
                                    input.set(String::new());
                                }
                            })
                            .child("Add")
                    )
            )
            .child(
                rect()
                    .children(
                        todos.read()
                            .iter()
                            .enumerate()
                            .map(|(i, todo)| {
                                rect()
                                    .key(i)
                                    .horizontal()
                                    .child(label().text(todo.clone()))
                            })
                    )
            )
    }
}

Next Steps

State Management

Deep dive into state management

Components

Build components with hooks

Events

Handle user interactions

Animation

Learn about animation hooks

Build docs developers (and LLMs) love