For Chinese version, see README_zh.md
A Rust library that adds React-style Hook system to the GPUI framework.
- React-style Hooks:
use_state,use_effect,use_memo - Attribute Macro:
#[hook_element]automatically adds Hook support to structs - Type Safety: Full Rust type system support
- Zero-cost Abstraction: Compile-time hook management, minimal runtime overhead
- GPUI Integration: Seamless integration with GPUI's
Rendertrait
Add to your Cargo.toml:
[dependencies]
gpui-hooks = "0.1"Note: This library requires the GPUI framework.
use gpui::{div, prelude::*, px, rgb, size, App, Application, Bounds, Context, Window, WindowBounds, WindowOptions};
use gpui_hooks::{hook_element, HookedRender};
use gpui_hooks::hooks::{UseEffectHook, UseMemoHook, UseStateHook};
#[hook_element]
struct CounterApp {}
impl HookedRender for CounterApp {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// useState - manage counter state
let (count, set_count) = self.use_state(|| 0i32);
// useMemo - compute doubled value
let count_val = count();
let doubled = self.use_memo(|| count_val * 2, [count_val]);
// useEffect - side effect when count changes
self.use_effect(|| {
println!("Effect: count changed to {}", count_val);
Some(|| println!("Effect cleanup"))
}, [count_val]);
div()
.child(format!("Count: {}", count()))
.child(format!("Doubled (useMemo): {}", doubled()))
.child(div().child("click me").on_click(cx.listener(
move |_this, _, _window, cx| {
set_count(count() + 1);
cx.notify();
},
)))
}
}fn main() {
Application::new().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|_, cx| {
cx.new(|_| CounterApp::default())
},
).unwrap();
});
}cargo run --example basicManages component state.
let (value, set_value) = self.use_state(|| initial_value);- Parameters: Closure returning initial value
- Returns:
(getter, setter)tuple - Type Constraint:
T: Clone + 'static
Executes side effects.
self.use_effect(|| {
// Side effect logic
Some(|| {
// Cleanup function (optional)
})
}, deps);- Parameters:
deps: Dependency array, re-executes when dependencies changeeffect: Side effect closure, returns optional cleanup function
- Note: Components must call
cleanup_effects()in theirDropimplementation
Memoizes computed values.
let memoized = self.use_memo(|| compute_expensive_value(), deps);- Parameters:
deps: Dependency array, re-computes when dependencies changecompute: Computation closure
- Returns:
getterfunction returning memoized value
Attribute macro that automatically adds Hook support to structs.
#[hook_element]
struct MyComponent {
// Custom fields
}The macro automatically:
- Adds
_hooks,_hook_index,_prevfields - Implements
Defaulttrait - Implements
HookedElementtrait - Implements
gpui::Rendertrait
Basic trait for hook components, providing hook management functionality.
Extends gpui::Render with hook lifecycle management.
❌ Wrong example:
if condition {
let (value, set_value) = self.use_state(|| 0); // Wrong!
}✅ Correct example:
let (value, set_value) = self.use_state(|| 0);
if condition {
// Use value()
}Each render must call the same number of hooks in the same order.
Components using use_effect must clean up in their Drop implementation:
impl Drop for MyComponent {
fn drop(&mut self) {
self.cleanup_effects();
}
}Create reusable custom hooks:
trait UseCounter {
fn use_counter(&self, initial: i32) -> (Box<dyn Fn() -> i32>, Box<dyn Fn(i32)>, Box<dyn Fn()>);
}
impl<T: UseStateHook> UseCounter for T {
fn use_counter(&self, initial: i32) -> (Box<dyn Fn() -> i32>, Box<dyn Fn(i32)>, Box<dyn Fn()>) {
let (count, set_count) = self.use_state(|| initial);
let increment = {
let count = count.clone();
let set_count = set_count.clone();
Box::new(move || set_count(count() + 1))
};
(count, set_count, increment)
}
}impl HookedRender for MyComponent {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (count, set_count) = self.use_state(|| 0);
let (name, set_name) = self.use_state(|| String::from("World"));
self.use_effect(|| {
println!("Count is now: {}", count());
None
}, [count()]);
// ... rendering logic
}
}cargo build
cargo build --releasecargo testcargo clippy
cargo fmt --checkcargo doc --openContributions are welcome! Please see CONTRIBUTING.md (to be created).
- Fork the project
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License. See the LICENSE file for details.
For questions or suggestions, please:
- Submit an Issue
- Join the discussion
Happy Hooking! 🎣