# `Mob.Component`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/component.ex#L1)

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`.

# `handle_event`
*optional* 

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

# `handle_info`
*optional* 

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

# `mount`

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

# `render`

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

# `terminate`
*optional* 

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

# `update`
*optional* 

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

# `expand`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
