Search documentation of mob

# Mob.Component behaviour (mob v0.6.18)

Copy Markdown View Source

Behaviour for native view components.

A component is a stateful Elixir process paired with a platform-native view registered by name on iOS/Android. The BEAM owns the state; the native side owns the rendering.

## Lifecycle

1. The parent screen declares `Mob.UI.native_view(MyComponent, id: :my_id, ...)`
2. On first render, a `Mob.ComponentServer` process is started and `mount/2` is called
3. `render/1` is called to get the props map forwarded to the native factory
4. On subsequent renders, `update/2` is called with new props from the parent
5. When the native view fires an event, `handle_event/3` is called
6. After any state change, `render/1` is re-called and native props are updated
7. When the component leaves the tree, the process is stopped and `terminate/2` is called

## Usage

    defmodule MyApp.ChartComponent do
      use Mob.Component

      def mount(props, socket) do
        {:ok, Mob.Socket.assign(socket, :data, props[:data])}
      end

      def render(assigns) do
        %{data: assigns.data}
      end

      def handle_event("segment_tapped", %{"index" => i}, socket) do
        {:noreply, Mob.Socket.assign(socket, :selected, i)}
      end
    end

## Native registration

Register the view factory at app startup:

    # iOS (Swift) — strip "Elixir." prefix and replace "." with "_":
    MobNativeViewRegistry.shared.register("MyApp_ChartComponent") { props, send in
        AnyView(ChartView(data: props["data"]) { index in
            send("segment_tapped", ["index": index])
        })
    }

    # Android (Kotlin):
    MobNativeViewRegistry.register("MyApp_ChartComponent") { props, send ->
        ChartView(data = props["data"]) { index ->
            send("segment_tapped", mapOf("index" to index))
        }
    }

## Declaration

    Mob.UI.native_view(MyApp.ChartComponent, id: :revenue_chart, data: @points)

The `:id` must be unique per screen. Duplicate ids on the same screen raise at render time.

## Stateless components

If a component has no internal state, omit `mount/2` and `handle_info/2`. The default `handle_event/3` raises for any event — add clauses for the events your native view fires, or delegate to the parent screen by forwarding via `send/2`.

## Summary

### Callbacks

handle_event(event, payload, socket)
handle_info(message, socket)
mount(props, socket)
render(assigns)
terminate(reason, socket)
update(props, socket)

### Functions

expand(tree, screen_pid, platform)

Walk a node tree, expanding `:native_view` nodes into serialisable form.

## Callbacks

# handle_event(event, payload, socket)

(optional)

    @callback handle_event(event :: String.t(), payload :: map(), socket :: Mob.Socket.t()) ::
      {:noreply, Mob.Socket.t()}

# handle_info(message, socket)

(optional)

    @callback handle_info(message :: term(), socket :: Mob.Socket.t()) ::
      {:noreply, Mob.Socket.t()}

# mount(props, socket)

    @callback mount(props :: map(), socket :: Mob.Socket.t()) ::
      {:ok, Mob.Socket.t()} | {:error, term()}

# render(assigns)

    @callback render(assigns :: map()) :: map()

# terminate(reason, socket)

(optional)

    @callback terminate(reason :: term(), socket :: Mob.Socket.t()) :: term()

# update(props, socket)

(optional)

    @callback update(props :: map(), socket :: Mob.Socket.t()) :: {:ok, Mob.Socket.t()}

## Functions

# expand(tree, screen_pid, platform)

    @spec expand(map(), pid(), atom()) :: {map(), MapSet.t()}

Walk a node tree, expanding `:native_view` nodes into serialisable form.

Starts or updates component processes, collects their rendered props, and injects the NIF handle. Returns `{expanded_tree, active_keys}` where `active_keys` is a `MapSet` of `{id, module}` pairs seen in this render — used by the screen to stop components that have left the tree.