gpui-patterns

This skill provides comprehensive guidance on common GPUI patterns and best practices for building maintainable, performant applications.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "gpui-patterns" with this command: npx skills add geoffjay/claude-plugins/geoffjay-claude-plugins-gpui-patterns

GPUI Patterns

Metadata

This skill provides comprehensive guidance on common GPUI patterns and best practices for building maintainable, performant applications.

Instructions

Component Composition Patterns

Basic Component Structure

use gpui::*;

// View component with state struct MyView { state: Model<MyState>, _subscription: Subscription, }

impl MyView { fn new(state: Model<MyState>, cx: &mut ViewContext<Self>) -> Self { let subscription = cx.observe(&state, |, _, cx| cx.notify()); Self { state, _subscription } } }

impl Render for MyView { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { let state = self.state.read(cx);

    div()
        .flex()
        .flex_col()
        .child(format!("Value: {}", state.value))
}

}

Container/Presenter Pattern

Container (manages state and logic):

struct Container { model: Model<AppState>, _subscription: Subscription, }

impl Container { fn new(model: Model<AppState>, cx: &mut ViewContext<Self>) -> Self { let subscription = cx.observe(&model, |, _, cx| cx.notify()); Self { model, _subscription } } }

impl Render for Container { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { let state = self.model.read(cx);

    // Pass data to presenter
    Presenter::new(state.data.clone())
}

}

Presenter (pure rendering):

struct Presenter { data: String, }

impl Presenter { fn new(data: String) -> Self { Self { data } } }

impl Render for Presenter { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { div().child(self.data.as_str()) } }

Compound Components

// Parent component with shared context pub struct Tabs { items: Vec<TabItem>, active_index: usize, }

pub struct TabItem { label: String, content: Box<dyn Fn() -> AnyElement>, }

impl Tabs { pub fn new() -> Self { Self { items: Vec::new(), active_index: 0, } }

pub fn add_tab(
    mut self,
    label: impl Into&#x3C;String>,
    content: impl Fn() -> AnyElement + 'static,
) -> Self {
    self.items.push(TabItem {
        label: label.into(),
        content: Box::new(content),
    });
    self
}

fn set_active(&#x26;mut self, index: usize, cx: &#x26;mut ViewContext&#x3C;Self>) {
    self.active_index = index;
    cx.notify();
}

}

impl Render for Tabs { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { div() .flex() .flex_col() .child( // Tab headers div() .flex() .children( self.items.iter().enumerate().map(|(i, item)| { tab_header(&item.label, i == self.active_index, || { self.set_active(i, cx) }) }) ) ) .child( // Active tab content (self.items[self.active_index].content)() ) } }

State Management Strategies

Model-View Pattern

// Model: Application state #[derive(Clone)] struct AppState { count: usize, items: Vec<String>, }

// View: Observes and renders state struct AppView { state: Model<AppState>, _subscription: Subscription, }

impl AppView { fn new(state: Model<AppState>, cx: &mut ViewContext<Self>) -> Self { let subscription = cx.observe(&state, |, _, cx| cx.notify()); Self { state, _subscription } }

fn increment(&#x26;mut self, cx: &#x26;mut ViewContext&#x3C;Self>) {
    self.state.update(cx, |state, cx| {
        state.count += 1;
        cx.notify();
    });
}

}

Context-Based State

// Global state via context #[derive(Clone)] struct GlobalSettings { theme: Theme, language: String, }

impl Global for GlobalSettings {}

// Initialize in app fn init_app(cx: &mut AppContext) { cx.set_global(GlobalSettings { theme: Theme::Light, language: "en".to_string(), }); }

// Access in components impl Render for MyView { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { let settings = cx.global::<GlobalSettings>();

    div()
        .child(format!("Language: {}", settings.language))
}

}

Subscription Patterns

Basic Subscription:

struct Observer { model: Model<Data>, _subscription: Subscription, }

impl Observer { fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self { let subscription = cx.observe(&model, |, _, cx| { cx.notify(); // Rerender on change });

    Self { model, _subscription }
}

}

Selective Updates:

impl Observer { fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self { let _subscription = cx.observe(&model, |this, model, cx| { let data = model.read(cx);

        // Only rerender if specific field changed
        if data.important_field != this.cached_field {
            this.cached_field = data.important_field.clone();
            cx.notify();
        }
    });

    Self {
        model,
        cached_field: String::new(),
        _subscription,
    }
}

}

Multiple Subscriptions:

struct MultiObserver { model_a: Model<DataA>, model_b: Model<DataB>, _subscriptions: Vec<Subscription>, }

impl MultiObserver { fn new( model_a: Model<DataA>, model_b: Model<DataB>, cx: &mut ViewContext<Self>, ) -> Self { let mut subscriptions = Vec::new();

    subscriptions.push(cx.observe(&#x26;model_a, |_, _, cx| cx.notify()));
    subscriptions.push(cx.observe(&#x26;model_b, |_, _, cx| cx.notify()));

    Self {
        model_a,
        model_b,
        _subscriptions: subscriptions,
    }
}

}

Event Handling Patterns

Click Events

div() .on_click(cx.listener(|this, event: &ClickEvent, cx| { // Handle click this.handle_click(cx); })) .child("Click me")

Keyboard Events

div() .on_key_down(cx.listener(|this, event: &KeyDownEvent, cx| { match event.key.as_str() { "Enter" => this.submit(cx), "Escape" => this.cancel(cx), _ => {} } }))

Event Propagation

// Stop propagation div() .on_click(|event, cx| { event.stop_propagation(); // Handle click })

// Prevent default div() .on_key_down(|event, cx| { if event.key == "Tab" { event.prevent_default(); // Custom tab handling } })

Mouse Events

div() .on_mouse_down(cx.listener(|this, event, cx| { this.mouse_down_position = Some(event.position); })) .on_mouse_move(cx.listener(|this, event, cx| { if let Some(start) = this.mouse_down_position { let delta = event.position - start; this.handle_drag(delta, cx); } })) .on_mouse_up(cx.listener(|this, event, cx| { this.mouse_down_position = None; }))

Action System

Define Actions

use gpui::*;

actions!(app, [ Increment, Decrement, Reset, SetValue ]);

// Action with data #[derive(Clone, PartialEq)] pub struct SetValue { pub value: i32, }

impl_actions!(app, [SetValue]);

Register Action Handlers

impl Counter { fn register_actions(&mut self, cx: &mut ViewContext<Self>) { cx.on_action(cx.listener(|this, _: &Increment, cx| { this.model.update(cx, |state, cx| { state.count += 1; cx.notify(); }); }));

    cx.on_action(cx.listener(|this, _: &#x26;Decrement, cx| {
        this.model.update(cx, |state, cx| {
            state.count = state.count.saturating_sub(1);
            cx.notify();
        });
    }));

    cx.on_action(cx.listener(|this, action: &#x26;SetValue, cx| {
        this.model.update(cx, |state, cx| {
            state.count = action.value;
            cx.notify();
        });
    }));
}

}

Dispatch Actions

// From within component fn handle_button_click(&mut self, cx: &mut ViewContext<Self>) { cx.dispatch_action(Increment); }

// With data fn set_specific_value(&mut self, value: i32, cx: &mut ViewContext<Self>) { cx.dispatch_action(SetValue { value }); }

// Global action dispatch cx.dispatch_action_on_window(Reset, window_id);

Keybindings

// Register global keybindings fn register_keybindings(cx: &mut AppContext) { cx.bind_keys([ KeyBinding::new("cmd-+", Increment, None), KeyBinding::new("cmd--", Decrement, None), KeyBinding::new("cmd-0", Reset, None), ]); }

Element Composition

Builder Pattern

fn card(title: &str, content: impl IntoElement) -> impl IntoElement { div() .flex() .flex_col() .bg(white()) .border_1() .rounded_lg() .shadow_sm() .p_6() .child( div() .text_lg() .font_semibold() .mb_4() .child(title) ) .child(content) }

Conditional Rendering

div() .when(condition, |this| { this.bg(blue_500()) }) .when_some(optional_value, |this, value| { this.child(format!("Value: {}", value)) }) .map(|this| { if complex_condition { this.border_1() } else { this.border_2() } })

Dynamic Children

div() .children( items.iter().map(|item| { div().child(item.name.as_str()) }) )

View Lifecycle

Initialization

impl MyView { fn new(cx: &mut ViewContext<Self>) -> Self { // Initialize state let model = cx.new_model(|_| MyState::default());

    // Set up subscriptions
    let subscription = cx.observe(&#x26;model, |_, _, cx| cx.notify());

    // Spawn async tasks
    cx.spawn(|this, mut cx| async move {
        // Async initialization
    }).detach();

    Self {
        model,
        _subscription: subscription,
    }
}

}

Update Notifications

impl MyView { fn update_state(&mut self, new_data: Data, cx: &mut ViewContext<Self>) { self.model.update(cx, |state, cx| { state.data = new_data; cx.notify(); // Trigger rerender }); } }

Cleanup

impl Drop for MyView { fn drop(&mut self) { // Manual cleanup if needed // Subscriptions are automatically dropped } }

Reactive Patterns

Derived State

impl Render for MyView { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { let state = self.model.read(cx);

    // Compute derived values
    let total = state.items.iter().map(|i| i.value).sum::&#x3C;i32>();
    let average = total / state.items.len() as i32;

    div()
        .child(format!("Total: {}", total))
        .child(format!("Average: {}", average))
}

}

Async Updates

impl MyView { fn load_data(&mut self, cx: &mut ViewContext<Self>) { let model = self.model.clone();

    cx.spawn(|_, mut cx| async move {
        let data = fetch_data().await?;

        cx.update_model(&#x26;model, |state, cx| {
            state.data = data;
            cx.notify();
        })?;

        Ok::&#x3C;_, anyhow::Error>(())
    }).detach();
}

}

Resources

Official Documentation

Common Patterns Reference

  • Model-View: State management pattern

  • Container-Presenter: Separation of concerns

  • Compound Components: Related components working together

  • Action System: Command pattern for user interactions

  • Subscriptions: Observer pattern for reactive updates

Best Practices

  • Store subscriptions to prevent cleanup

  • Use cx.notify() sparingly

  • Prefer composition over inheritance

  • Keep render methods pure

  • Handle errors gracefully

  • Document component APIs

  • Test component behavior

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

documentation-update

No summary provided by upstream source.

Repository SourceNeeds Review
General

git-troubleshooting

No summary provided by upstream source.

Repository SourceNeeds Review
General

git-advanced

No summary provided by upstream source.

Repository SourceNeeds Review