Components
Components are the building blocks of Hologram applications. They are reusable pieces of UI that can be composed together to create complex interfaces.
Basic Example
Here's a simple stateless component that displays a greeting:
defmodule Greeting do
use Hologram.Component
prop :name, :string
prop :title, :string, default: "Mr."
def template do
~HOLO"""
<p>Hello, {@title} {@name}!</p>
"""
end
end
Usage:
<Greeting name="Wick" title="Mr." />
Props
Props are the primary way to pass data to components.
They are defined using the prop/2
macro receiving name
and type
params:
prop :user_id, :integer
or prop/3
macro that can receive the third opts
param:
prop :user_id, :integer, default: 123
Types
Props can be typed using the following types:
:any
- Any type (no type checking):atom
- Atoms:boolean
- Booleans:bitstring
- Bitstrings:float
- Floats:function
- Functions:integer
- Integers:list
- Lists:map
- Maps:pid
- PIDs:port
- Ports:reference
- References:string
- Strings (UTF-8 encoded binaries):tuple
- tuples
Options
Props can be configured using the following options:
Default Value
Set a default value for optional props using the default
option:
prop :count, :integer, default: 0
Context Source
Props can be sourced from the Context using the from_context
option. This is useful for accessing shared data across components:
prop :user, :map, from_context: :current_user
For more information about Context, see the Context page.
Stateful vs. Stateless Components
Components can be either stateful or stateless:
- Stateless Components: Don't maintain any internal state. They render based solely on their props.
- Stateful Components: Maintain internal state and can update it over time. They are identified by a unique
cid
(component ID) and can have actions and commands. Stateful components can be initialized using eitherinit/3
(server-side) orinit/2
(client-side) functions.
Component ID (cid)
To make a component stateful, provide a cid
when using it:
<Assassin cid="baba_yaga" name="John Wick" kill_count={439} />
The cid
is used to target actions and commands at specific components using the target
field:
<button $click={action: :increment_kills, target: "baba_yaga"}>Add Another One</button>
Initialization
A stateful component is initialized using either init/3
or init/2
, depending on whether it is first rendered on the server or client:
Server-side Initialization
The init/3
function is used when the component is first loaded on the server. It receives three parameters:
props
- a map containing the component's propscomponent
- aComponent
struct representing the client-side state containerserver
- aServer
struct representing the server-side state container
The function should return either:
- A tuple containing
Component
andServer
structs when modifying both client and server state - Just a
Component
struct when only modifying client state - Just a
Server
struct when only modifying server state
Example:
def init(_props, component, server) do
component = put_state(component, :kill_count, 439)
server = put_session(server, :name, "John Wick")
{component, server}
end
Client-side Initialization
The init/2
function is used when the component is first loaded on the client. It receives two parameters:
props
- a map containing the component's propscomponent
- aComponent
struct representing the client-side state container
The function should return a Component
struct with any initial state:
def init(_props, component) do
put_state(component, kill_count: 439, name: "John Wick")
end
Slots
Slots allow components to accept and render child content. They are defined using the <slot />
tag in the component's template:
defmodule Card do
use Hologram.Component
def template do
~HOLO"""
<div class="card">
<slot />
</div>
"""
end
end
Usage:
<Card>
<h1>Card Title</h1>
<p>Card content</p>
</Card>
Templates
Defining Templates
Hologram components can be templated using either a template function within the component module or a separate .holo
file. These methods offer the same features and syntax - your choice depends on your team's coding style and project structure.
Template Function
def template do
~HOLO"""
<div>Hello, {@name}!</div>
"""
end
Colocated .holo File
Create a file with the same name as your component's module file but with .holo
extension (e.g., if your component is in my_component.ex
, create my_component.holo
). The .holo
file must be located in the same directory as the module file.
Template Syntax
See the Template Syntax documentation for more details.
Event Handling
Stateful components can handle user interactions and business logic through two mechanisms:
- Actions: client-side operations that update local state
- Commands: server-side operations that can perform remote tasks
Example:
def action(:increment, params, component) do
put_state(component, :count, component.state.count + params.by)
end
def command(:insert_user, params, server) do
{:ok, user} = Repo.insert(%User{first_name: params.first_name})
put_action(server, :user_inserted, user: user)
end
See the Actions and Commands documentation for more details.