Props and events are used by components to communicate with each other. Props are how components communicate with their children, and events are how components communicate with their parents in the component hierarchy.
Props are passed to components as arguments to the component function. Event callbacks are are also passed to components as props in the form of a function. The component can then call the event callback when an event occurs, notifying the parent.
Let's take a look at how props and events work in a functional example. In this example, the
counter
component is passing a prop called on_click
to the two button
components. The counter
component is also passing a prop called count
to the display
component. When the button
components are clicked, they call the on_click
event handler that was passed to them as a prop.
The counter
component then increments its count
state and re-renders. The display
component is
also re-rendered because its count
prop has changed.
import gleam/int import gleam/option.{None, Option, Some}
import sprocket/context.{Context, dep}
import sprocket/hooks.{reducer, handler}
import sprocket/component.{component, render}
import sprocket/html/elements.{div, span, text}
import sprocket/html/attributes.{class, classes}
type Model =
Int
type Msg {
UpdateCounter(Int) ResetCounter
}
fn update(_model: Model, msg: Msg) -> Model {
case msg {
UpdateCounter(count) -> {
count
} ResetCounter -> 0
}
}
pub type CounterProps {
CounterProps
}
pub fn counter(ctx: Context, _props: CounterProps) {
// Define a reducer to handle events and update the state use ctx, count, dispatch <- reducer(ctx,
0, update)
render(
ctx, div(
[class("flex flex-row m-4")], [
component(
button, StyledButtonProps(
class: "rounded-l", label: "-", on_click: fn() { dispatch(UpdateCounter(count - 1)) },
),
), component(
display, DisplayProps(count: count),
), component(
button, StyledButtonProps(
class: "rounded-r", label: "+", on_click: fn() { dispatch(UpdateCounter(count + 1)) },
),
),
],
),
)
}
pub type ButtonProps {
ButtonProps(label: String, on_click: fn() -> Nil) StyledButtonProps(class: String, label: String,
on_click: fn() -> Nil)
}
pub fn button(ctx: Context, props: ButtonProps) {
// here we unpack the different types of ButtonProps that can be passed to the button component
let #(class, label, on_click) = case props {
ButtonProps(label, on_click) -> #(None, label, on_click) StyledButtonProps(class, label,
on_click) -> #(Some(class), label, on_click)
}
use ctx, handle_click <- handler(
ctx, fn(_) { on_click() },
)
render(
ctx, html.button_text(
[
attributes.on_click(handle_click), classes([
class, Some(
"p-1 px-2 border dark:border-gray-500 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200
dark:hover:bg-gray-700 active:bg-gray-300 dark:active:bg-gray-600",
),
]),
], label,
),
)
}
pub type DisplayProps {
DisplayProps(count: Int)
}
pub fn display(ctx: Context, props: DisplayProps) {
let DisplayProps(count: count) = props
render(
ctx, span(
[
class(
"p-1 px-2 w-10 bg-white dark:bg-gray-900 border-t border-b dark:border-gray-500
align-center text-center",
),
], [text(int.to_string(count))],
),
)
}
So state flows down the component tree while events bubble up. We'll cover state management more in-depth in the next section, but it's useful to start thinking about how this data-flow will inform where state should live in your component hierarchy. And since we have the safety provided by the Gleam type system, we aren't afraid of refactoring our state to a different part of the hierarchy when our requirements or designs inevitably change!