docs-components.md
Components are the building blocks of Hologram applications. They are reusable pieces of UI that can be composed together to create complex interfaces.
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 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
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 - tuplesProps can be configured using the following options:
Set a default value for optional props using the default option:
prop :count, :integer, default: 0
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.
Components can be either stateful or stateless:
cid (component ID) and can have actions and commands. Stateful components can be initialized using either init/3 (server-side) or init/2 (client-side) functions.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>
Important: Each stateful component instance is initialized exactly once during its lifetime. The initialization method depends on where the component's lifecycle starts:
init/3: When the component's lifecycle starts as part of server-side page rendering (pages are always first rendered on the server when you navigate to them)init/2: When the component's lifecycle starts by being dynamically added to an already-loaded pageA component module can define both functions since you may have multiple instances - some starting on the server and others starting directly on the client. Each instance has its own unique cid and separate state.
Called when the component starts its lifecycle on the server. Provides access to cookies and session data through the Server struct. It receives three parameters:
props - a map containing the component's propscomponent - a Component struct representing the client-side state containerserver - a Server struct representing the server-side state container (with access to cookies and session)The function should return either:
Component and Server structs when modifying both client and server stateComponent struct when only modifying client stateServer struct when only modifying server stateExample:
def init(_props, component, server) do
component = put_state(component, :kill_count, 439)
server = put_session(server, :name, "John Wick")
{component, server}
end
Called when the component starts its lifecycle directly on the client. No access to cookies or session data since they are managed through the Server struct. It receives two parameters:
props - a map containing the component's propscomponent - a Component struct representing the client-side state containerThe function should return a Component struct with any initial state.
Example:
def init(_props, component) do
put_state(component, kill_count: 439, name: "John Wick")
end
In both init/3 and init/2 functions, you can chain actions that will be executed when the component is mounted on the client. This is useful for triggering setup logic, animations, or data loading after the component becomes available in the browser.
Server-side initialization (init/3):
def init(_props, component, _server) do
put_action(component, :setup_timer)
end
Client-side initialization (init/2):
def init(_props, component) do
put_action(component, :start_animation)
end
Both init/3 and init/2 functions are optional. If you don't need to initialize state or perform any preparation, you can omit them entirely - Hologram provides default implementations that simply return the Component and Server structs unchanged.
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>
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.
def template do
~HOLO"""
<div>Hello, {@name}!</div>
"""
end
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.
Unlike the template function approach, colocated .holo files contain only the template markup without the ~HOLO sigil:
<div>Hello, {@name}!</div>
See the Template Syntax documentation for more details.
Stateful components can handle user interactions and business logic through two mechanisms:
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.
Because commands run on the server, a component can guard its own commands with middleware attached to the component - useful for a distributed component that must protect its commands wherever it is embedded.
Sponsored by
Previous
Next