View Source Scenic.Scene behaviour (Scenic v0.11.2)

overview

Overview

Scenes are the core of the UI model.

A Scene is a type of GenServer that maintains state, handles input, events and other messages, and plays a role managing the supervision of components/controls such as buttons and other input.

a-brief-aside

A brief aside

Before saying anything else I want to emphasize that the rest of your application, meaning device control logic, sensor reading / writing, services, whatever, does not need to have anything to do with Scenes.

In many cases I recommend treating those as separate GenServers in their own supervision trees that you maintain. Then your Scenes would query or send information to/from them via cast or call messages.

Part of the point of using Elixir/Erlang/OTP is separating this sort of logic into independent trees. That way, an error in one part of your application does not mean the rest of it will fail.

scenes

Scenes

Scenes are the core of the UI model. A scene consists of one or more graphs, and a set of event handlers and filters to deal with user input and other messages.

Think of a scene as being a little like an HTML page. HTML pages have:

  • Structure (the DOM)
  • Logic (Javascript)
  • Links to other pages.

A Scene has:

  • Structure (graphs),
  • Logic (event handlers and filters)
  • Transitions to other scenes. Well... it can request the ViewPort to go to a different scene.

Your application is a collection of scenes that are in use at different times. There is only ever one scene showing in a ViewPort at a given time. However, scenes can instantiate components, effectively embedding their graphs inside the main one. More on that below.

graphs

Graphs

Each scene should maintain at least one graph. You can build graphs at compile time, or dynamically while your scene is running. Building them at compile time has two advantages

  1. Performance: It is clearly faster to build the graph once during build time than to build it repeatedly during runtime.
  2. Error checking: If your graph has an error in it, it is much better to have it stop compilation than cause an error during runtime.

Example of building a graph during compile time:

@graph Scenic.Graph.build(font_size: 24)
  |> button({"Press Me", :button_id}, translate: {20, 20})

Rather than having a single scene maintain a massive graph of UI, graphs can reference graphs in other scenes.

On a typical screen of UI, there is one scene that is the root. Each control, is its own scene process with its own state. These child scenes can in turn contain other child scenes. This allows for strong code reuse, isolates knowledge and logic to just the pieces that need it, and keeps the size of any given graph to a reasonable size. For example, the handlers of a check-box scene don't need to know anything about how a slider works, even though they are both used in the same parent scene. At best, they only need to know that they both conform to the Component.Input behavior, and can thus query or set each others value. Though it is usually the parent scene that does that.

The application developer is responsible for building and maintaining the scene's graph. It only enters the world of the ViewPort when you call push_graph. Once you have called push_graph, that graph is sent to the drivers and is out of your immediate control. You update that graph by calling push_graph again.

scene-structure

Scene Structure

The overall structure of a scene has several parts. It is a GenServer, which means you have an input function and can implement the GenServer callbacks such as handle_info/2, handle_cast/2, handle_call/3 and any others. The terms you return from those callbacks is pretty much what you expect with the requirement that the state is always a scene structure.

The init/3 callback takes 3 parameters. They are the scene structure (state), params that the parent scene set up, and opts, which includes things like the id, theme, and some styles that were set on the scene.

There are several additional callbacks that your scene can support. The main ones are handle_input/2, and handle_event/3. If you are making a Component (a reusable scene), then there are additional callbacks that allow it to play nicely with others.

scene-example

Scene Example

This example shows a simple scene that contains a button. When the button is clicked, the scene increments a counter and displays the number of clicks it has received.

defmodule MySimpleScene do
  use Scenic.Scene

  alias Scenic.Graph
  import Scenic.Primitives
  import Scenic.Components

  @initial_count  0

  # This graph is built at compile time so it doesn't do any work at runtime.
  # It could also be built in the init function (or any other) if you want
  # it to be dynamic based on the params or whatever.
  @graph Graph.build()
      |> group( fn graph ->
        graph
        |> text( "Count: " <> inspect(@initial_count), id: :count )
        |> button( "Click Me", id: :btn, translate: {0, 30} )
      end,
      translate: {100, 100}
    )

  # Simple function to return @graph.
  # @graph is built at compile time and stored directly in the BEAM file every
  # time it is used. A simple accessor function will cause it to be stored only once.
  # Do this when you build graphs at compile time to save space in your file.
  defp graph(), do: @graph

  # The Scenic.Scene init function
  @impl Scenic.Scene
  def init(scene, _params, _opts) do
    scene =
      scene
      |> assign( count: @initial_count )
      |> push_graph( graph() )
    {:ok, scene}
  end

  @impl Scenic.Scene
  def handle_event( {:click, :btn}, _, %{assigns: %{count: count}} = scene ) do
    count = count + 1

    # modify the graph to show the current click count
    graph =
      graph()
      |> Graph.modify( :count, &text(&1, "Count: " <> inspect(count)) )

    # update the count and push the modified graph
    scene =
      scene
      |> assign( count: count )
      |> push_graph( graph )

    # return the updated scene
    { :noreply, scene }
  end

end

scene-state

Scene State

Scenes are just a specialized form of GenServer. This means they can react to messages and can have state. However, scene state is a bit like socket state in Phoenix in that It has a strict format, but you can add anything you want into its :assigns map.

Multiple Graphs

Any given scene can maintain multiple graphs and multiple draw scripts. They are identified from each other with an id that you attach.

Normally when you use push_graph, you don't attach an ID. In that case the scene's id is used as the graph id.

The act of pushing a graph to the ViewPort causes it to be compiled into a script, which is stored in an ETS table so that the drivers can quickly access it. To use a second, or third, graph that you scene has pushed, refer to it using a Scenic.Primitive.Script primitive.

def init( scene, param, opts ) do
  second_graph = Scenic.Graph.build()
    |> text( "Text in the second graph" )

  main_graph = Scenic.Graph.build()
    |> script( "my_fancy_id" )

  scene =
    scene
    |> push_graph( main_graph )
    |> push_graph( second_graph, "my_fancy_id" )

  { :ok, scene }
end

Note that it doesn't matter which graph you push first. They will link to each other via the string id that you supply.

communications

Communications

Scenes are specialized GenServers. As such, they communicate with each other (and the rest of your application) through messages. You can receive messages with the standard handle_info, handle_cast, handle_call callbacks just like any other scene.

Scenes have two new event handling callbacks that you can optionally implement. These are about user input vs UI events.

input-vs-events

Input vs. Events

Input is data generated by the drivers and sent up to the scenes through Scenic.ViewPort. There is a limited set of input types and they are standardized so that the drivers can be built independently of the scenes. Input follows certain rules about which scene receives them.

Events are messages that one scene generates for consumption by other scenes. For example, a Scenic.Component.Button scene would generate a {:click, msg} event that is sent to its parent scene.

You can generate any message you want, however, the standard component libraries follow certain patterns to keep things sensible.

input-handling

Input Handling

You handle incoming input events by adding handle_input/3 callback functions to your scene. Each handle_input/3 call passes in the input message itself, an input context struct, and your scene's state. You can then take the appropriate actions, including generating events (below) in response.

Under normal operation, input that is not position dependent (keys, window events, more...) is sent to the root scene. Input that does have a screen position (cursor_pos, cursor button presses, etc.) is sent to the scene that contains the graph that was hit.

Your scene can "capture" all input of a given type so that it is sent to itself instead of the default scene for that type. this is how a text input field receives the key input. First, the user selects that field by clicking on it. In response to the cursor input, the text field captures text input (and maybe transforms its graph to show that it is selected).

Captured input types should be released when no longer needed so that normal operation can resume.

The input messages are passed on to a scene's parent if not processed.

event-filtering

Event Filtering

In response to input, (or anything else... a timer perhaps?), a scene can generate an event (any term), which is sent backwards up the tree of scenes that make up the current aggregate graph.

In this way, a Scenic.Component.Button scene can generate a {:click, msg} event that is sent to its parent. If the parent doesn't handle it, it is sent to that scene's parent. And so on until the event reaches the root scene. If the root scene also doesn't handle it then the event is dropped.

To handle events, you add handle_event/3 functions to your scene. This function handles the event, and stops its progress backwards up the graph. It can handle it and allow it to continue up the graph. Or it can transform the event and pass the transformed version up the graph.

You choose the behavior by returning either

{:cont, msg, state}

or

{:halt, state}

Parameters passed in to handle_event/3 are the event itself, a reference to the originating scene (which you can to communicate back to it), and your scene's state.

A pattern I'm using is to handle an event at the filter and stop its progression. It also generates and sends a new event to its parent. I do this instead of transforming and continuing when I want to change the originating scene.

no-children

No children

There is an optimization you can use. If you know for certain that your component will not attempt to use any components, you can set has_children to false like this.

use Scenic.Component, has_children: false

Setting has_children to false means the scene won't create a dynamic supervisor for this scene, which saves some resources and imporoves startup time.

For example, the Button component sets has_children to false.

Link to this section Summary

Callbacks

Invoked when the Scene receives an event from another scene.

Retrieve the current data associated with the scene and return it to the caller.

Get the current "value" associated with the scene and return it to the caller.

Invoked when the Scene receives input from a driver.

Put the current "value" associated with the scene .

Update the data and options of a scene. Usually implemented by Components.

Invoked when the Scene is started.

Functions

Convenience function to assign a list or map of values into a scene struct.

Convenience function to assign a value into a scene struct.

Convenience function to assign a list of new values into a scene struct.

Convenience function to assign a new values into a scene struct.

Request one or more types of input that a scene would otherwise not receive if not captured. This is rarely used by scenes and even then mostly for things like key events outside of a text field.

Cast a message to a scene's parent

Get the pid of the child with the specified id.

Get a list of {id, pid} pairs for all the scene's children.

Convenience function to fetch an assigned value out of a scene struct.

Fetch a list of input captured by the given scene.

Fetch the "data" of the child with the specified id.

Fetch a list of input requested by the given scene.

Fetch the "parent" matrix that positions this scene.

Convenience function to get an assigned value out of a scene struct.

Get the "value" of the child with the specified id.

Get the "parent" matrix that positions this scene.

Convert a point in global coordinates to scene local coordinates.

Convert a point in scene local coordinates to global coordinates.

Get the pid of the scene's parent.

Push a graph to the scene's ViewPort.

Push a named script to the scene's ViewPort.

Put the "value" of the child with the specified id.

release all currently requested input.

Request one or more types of input that a scene would otherwise not receive if not captured. This is rarely used by scenes and even then mostly for things like key events outside of a text field.

Cast a message to a scene's children

Send an event message to a specific scene

Send a message to a scene's parent

Send an event message to a scene's parent

Cleanly stop a scene from running

release all currently requested input.

Update the "data" of the child with the specified id.

Link to this section Types

@type response_opts() :: [timeout() | :hibernate | {:continue, term()}]
@type t() :: %Scenic.Scene{
  assigns: map(),
  child_supervisor: nil | map(),
  children: nil | map(),
  id: any(),
  module: atom(),
  parent: pid(),
  pid: pid(),
  stop_pid: pid(),
  supervisor: pid(),
  theme: atom() | map(),
  viewport: Scenic.ViewPort.t()
}

Link to this section Callbacks

Link to this callback

handle_event(event, from, scene)

View Source (optional)
@callback handle_event(event :: term(), from :: pid(), scene :: t()) ::
  {:noreply, scene}
  | {:noreply, scene}
  | {:noreply, scene, timeout()}
  | {:noreply, scene, :hibernate}
  | {:noreply, scene, opts :: response_opts()}
  | {:halt, scene}
  | {:halt, scene, timeout()}
  | {:halt, scene, :hibernate}
  | {:halt, scene, opts :: response_opts()}
  | {:cont, event, scene}
  | {:cont, event, scene, timeout()}
  | {:cont, event, scene, :hibernate}
  | {:cont, event, scene, opts :: response_opts()}
  | {:stop, reason, scene}
when scene: t(), reason: term(), event: term()

Invoked when the Scene receives an event from another scene.

Events are messages generated by a scene, that are passed backwards up the ViewPort's scene supervision tree. This is opposed to "input", which comes directly from the drivers.

When an event arrives at a scene, you can consume it, or pass it along to the scene above you in the ViewPort's supervision structure.

To consume the input and have processing stop afterward, return either a {:halt, ...} or {:noreply, ...} value. They are effectively the same thing.

To allow the scene's parent to process the input, return {:cont, event, state, ...}. Note that you can pass along the event unchanged or transform it in the process if you wish.

The callback supports all the return values of the init callback in Genserver.

In addition to the normal return values defined by GenServer, a Scene can add an optional {push: graph} term, which pushes the graph to the viewport.

This has replaced push_graph() as the preferred way to push a graph.

Link to this callback

handle_fetch(from, scene)

View Source (optional)
@callback handle_fetch(from :: GenServer.from(), scene :: t()) ::
  {:reply, reply, scene}
  | {:reply, reply, scene, timeout() | :hibernate | {:continue, term()}}
when reply: term(), scene: t()

Retrieve the current data associated with the scene and return it to the caller.

If this callback is not implemented, the caller with get an {:error, :not_implemented}.

Link to this callback

handle_get(from, scene)

View Source (optional)
@callback handle_get(from :: GenServer.from(), scene :: t()) ::
  {:reply, reply, scene}
  | {:reply, reply, scene, timeout() | :hibernate | {:continue, term()}}
when reply: term(), scene: t()

Get the current "value" associated with the scene and return it to the caller.

If this callback is not implemented, the caller with receive nil.

Link to this callback

handle_input(input, id, scene)

View Source (optional)
@callback handle_input(input :: Scenic.ViewPort.Input.t(), id :: any(), scene :: t()) ::
  {:noreply, scene}
  | {:noreply, scene}
  | {:noreply, scene, timeout()}
  | {:noreply, scene, :hibernate}
  | {:noreply, scene, opts :: response_opts()}
  | {:halt, scene}
  | {:halt, scene, timeout()}
  | {:halt, scene, :hibernate}
  | {:halt, scene, opts :: response_opts()}
  | {:cont, input, scene}
  | {:cont, input, scene, timeout()}
  | {:cont, input, scene, :hibernate}
  | {:cont, input, scene, opts :: response_opts()}
  | {:stop, reason, scene}
when scene: t(), reason: term(), input: term()

Invoked when the Scene receives input from a driver.

Input is messages sent directly from a driver, usually based on some action by the user. This is opposed to "events", which are generated by other scenes.

When input arrives at a scene, you can consume it, or pass it along to the scene above you in the ViewPort's supervision structure.

To consume the input and have processing stop afterward, return either a {:halt, ...} or {:noreply, ...} value. They are effectively the same thing.

To allow the scene's parent to process the input, return {:cont, input, state, ...}. Note that you can pass along the input unchanged or transform it in the process if you wish.

The callback supports all the return values of the init callback in Genserver.

In addition to the normal return values defined by GenServer, a Scene can add an optional {push: graph} term, which pushes the graph to the viewport.

This has replaced push_graph() as the preferred way to push a graph.

Link to this callback

handle_put(value, scene)

View Source (optional)
@callback handle_put(value :: any(), scene :: t()) ::
  {:noreply, scene}
  | {:noreply, scene, timeout() | :hibernate | {:continue, term()}}
when scene: t()

Put the current "value" associated with the scene .

Does nothing if this callback is not implemented.

Link to this callback

handle_update(data, opts, scene)

View Source (optional)
@callback handle_update(data :: any(), opts :: Keyword.t(), scene :: t()) ::
  {:noreply, scene}
  | {:noreply, scene, timeout() | :hibernate | {:continue, term()}}
when scene: t()

Update the data and options of a scene. Usually implemented by Components.

If this callback is not implemented, then changes to the component in the parent's graph will have no affect.

Link to this callback

init(scene, args, options)

View Source
@callback init(scene :: t(), args :: term(), options :: Keyword.t()) ::
  {:ok, scene}
  | {:ok, scene, timeout :: non_neg_integer()}
  | {:ok, scene, :hibernate}
  | {:ok, scene, opts :: response_opts()}
  | :ignore
  | {:stop, reason}
when scene: t(), reason: term()

Invoked when the Scene is started.

args is the argument term you passed in via config or ViewPort.set_root.

options is a list of information giving you context about the environment the scene is running in. If an option is not in the list, then it should be treated as nil.

  • :viewport - This is the pid of the ViewPort that is managing this dynamic scene. It will be not set, or nil, if you are managing the Scene in a static supervisor yourself.
  • :styles - This is the map of styles that your scene can choose to inherit (or not) from its parent scene. This is typically used by a child control that wants to visually fit into its parent's look.
  • :id - This is the :id term that the parent set a component when it was invoked.

The callback supports all the return values of the init callback in Genserver.

In addition to the normal return values defined by GenServer, a Scene can return two new ones that push a graph to the viewport

Returning {:ok, state, push: graph} will push the indicated graph to the ViewPort. This is preferable to the old push_graph() function.

Link to this section Functions

@spec assign(scene :: t(), assigns :: Keyword.t() | map()) :: t()

Convenience function to assign a list or map of values into a scene struct.

Link to this function

assign(scene, key, value)

View Source
@spec assign(scene :: t(), key :: any(), value :: any()) :: t()

Convenience function to assign a value into a scene struct.

Link to this function

assign_new(scene, key_list)

View Source
@spec assign_new(scene :: t(), key_list :: Keyword.t() | map()) :: t()

Convenience function to assign a list of new values into a scene struct.

Only values that do not already exist will be assigned

Link to this function

assign_new(scene, key, value)

View Source
@spec assign_new(scene :: t(), key :: any(), value :: any()) :: t()

Convenience function to assign a new values into a scene struct.

The value will only be assigned if it does not already exist in the struct.

Link to this function

capture_input(scene, input_class)

View Source
@spec capture_input(
  scene :: t(),
  input_class :: Scenic.ViewPort.Input.class() | [Scenic.ViewPort.Input.class()]
) :: :ok | {:error, atom()}

Request one or more types of input that a scene would otherwise not receive if not captured. This is rarely used by scenes and even then mostly for things like key events outside of a text field.

Any input types that were previously requested that are no longer in the request list are dropped. Request [] to cancel all input requests.

returns :ok or an error

This is intended be called by a Scene process, but doesn't need to be.

Link to this function

cast_children(scene, msg)

View Source
@spec cast_children(scene :: t(), msg :: any()) :: :ok | {:error, :no_children}
@spec cast_parent(scene :: t(), msg :: any()) :: :ok

Cast a message to a scene's parent

@spec child(scene :: t(), id :: any()) ::
  {:ok, [child_pid :: pid()]} | {:error, :no_children}

Get the pid of the child with the specified id.

You can specify the same ID to more than one child. This is why the return is a list.

@spec children(scene :: t()) ::
  {:ok, [{id :: any(), child_pid :: pid()}]} | {:error, :no_children}

Get a list of {id, pid} pairs for all the scene's children.

@spec fetch(scene :: t(), key :: any()) :: {:ok, any()} | :error

Convenience function to fetch an assigned value out of a scene struct.

@spec fetch_captures(scene :: t()) ::
  {:ok, [Scenic.ViewPort.Input.class()]} | {:error, atom()}

Fetch a list of input captured by the given scene.

This is intended be called by a Scene process, but doesn't need to be.

@spec fetch_child(scene :: t(), id :: any()) ::
  {:ok, [child_pid :: pid()]} | {:error, :no_children}

Fetch the "data" of the child with the specified id.

This function is intended to be used to query the current data of a component. The component must have implemented the Scenic.Scene.handle_fetch/2 callback. All of the built-in components support this.

Unlike get_child, fetch_child returns the full data associated with component, not just the current value. For example a checkbox component might fetch the value {"My Checkbox", true}

You can specify the same ID to more than one child. This is why the return is a list.

@spec fetch_requests(scene :: t()) ::
  {:ok, [Scenic.ViewPort.Input.class()]} | {:error, atom()}

Fetch a list of input requested by the given scene.

This is intended be called by a Scene process, but doesn't need to be.

@spec fetch_transform(scene :: t()) ::
  {:ok, Scenic.Math.matrix()} | {:error, :not_found}

Fetch the "parent" matrix that positions this scene.

This matrix can be used to move from scene "local" coordinates to global coordinates.

Link to this function

get(scene, key, default \\ nil)

View Source
@spec get(scene :: t(), key :: any(), default :: any()) :: any()

Convenience function to get an assigned value out of a scene struct.

@spec get_child(scene :: t(), id :: any()) ::
  {:ok, [child_pid :: pid()]} | {:error, :no_children}

Get the "value" of the child with the specified id.

This function is intended to be used to query the current value of a component. The component must have implemented the Scenic.Scene.handle_get/2 callback. All of the built-in components support this.

For example, you could use this to query the current value of a checkbox.

You can specify the same ID to more than one child. This is why the return is a list.

@spec get_transform(scene :: t()) :: Scenic.Math.matrix()

Get the "parent" matrix that positions this scene.

This matrix can be used to move from scene "local" coordinates to global coordinates.

Link to this function

global_to_local(scene, arg)

View Source
@spec global_to_local(scene :: t(), Scenic.Math.point()) :: Scenic.Math.point()

Convert a point in global coordinates to scene local coordinates.

Link to this function

handle_call(msg, from, scene)

View Source
Link to this function

local_to_global(scene, arg)

View Source
@spec local_to_global(scene :: t(), Scenic.Math.point()) :: Scenic.Math.point()

Convert a point in scene local coordinates to global coordinates.

@spec parent(scene :: t()) :: {:ok, parent :: pid()}

Get the pid of the scene's parent.

Link to this function

push_graph(scene, graph, id \\ nil)

View Source
@spec push_graph(scene :: t(), graph :: Scenic.Graph.t(), name :: String.t() | nil) ::
  t()

Push a graph to the scene's ViewPort.

This function compiles a graph into a script, registers any requested inputs and stores it all in the ViewPort's ETS tables.

Any components that are created or removed from the scene are started/stopped/updated as appropriate.

Link to this function

push_script(scene, script, name, opts \\ [])

View Source
@spec push_script(
  scene :: t(),
  script :: Scenic.Script.t(),
  name :: String.t(),
  opts :: Keyword.t()
) :: t()

Push a named script to the scene's ViewPort.

Link to this function

put_child(scene, id, value)

View Source
@spec put_child(scene :: t(), id :: any(), value :: any()) ::
  :ok | {:error, :no_children}

Put the "value" of the child with the specified id.

This function is intended to be used to change the current value of a component. The component must have implemented the Scenic.Scene.handle_put/2 callback. All of the built-in components support this.

For example, you could use this to change the current value of a checkbox. In this case, the returned value would be true or false.

You can specify the same ID to more than one child. This will cause all the components with that id to receive the handle_put call.

Link to this function

release_input(scene, input_class \\ :all)

View Source
@spec release_input(
  scene :: t(),
  input_class ::
    Scenic.ViewPort.Input.class() | [Scenic.ViewPort.Input.class()] | :all
) :: :ok | {:error, atom()}

release all currently requested input.

This is intended be called by a Scene process, but doesn't need to be.

Link to this function

request_input(scene, input_class)

View Source
@spec request_input(
  scene :: t(),
  input_class :: Scenic.ViewPort.Input.class() | [Scenic.ViewPort.Input.class()]
) :: :ok | {:error, atom()}

Request one or more types of input that a scene would otherwise not receive if not captured. This is rarely used by scenes and even then mostly for things like key events outside of a text field.

Any input types that were previously requested that are no longer in the request list are dropped. Request [] to cancel all input requests.

returns :ok or an error

This is intended be called by a Scene process, but doesn't need to be.

Link to this function

send_children(scene, msg)

View Source
@spec send_children(scene :: t(), msg :: any()) :: :ok | {:error, :no_children}

Cast a message to a scene's children

Link to this function

send_event(pid, event_msg)

View Source
@spec send_event(pid :: pid(), event :: any()) :: :ok

Send an event message to a specific scene

@spec send_parent(scene :: t(), msg :: any()) :: :ok

Send a message to a scene's parent

Link to this function

send_parent_event(scene, event_msg)

View Source
@spec send_parent_event(scene :: t(), event :: any()) :: :ok

Send an event message to a scene's parent

@spec stop(scene :: t()) :: :ok

Cleanly stop a scene from running

Link to this function

terminate(reason, scene)

View Source
Link to this function

unrequest_input(scene, input_class \\ :all)

View Source
@spec unrequest_input(
  scene :: t(),
  input_class ::
    Scenic.ViewPort.Input.class() | [Scenic.ViewPort.Input.class()] | :all
) :: :ok | {:error, atom()}

release all currently requested input.

This is intended be called by a Scene process, but doesn't need to be.

Link to this function

update_child(scene, id, value, opts \\ [])

View Source
@spec update_child(scene :: t(), id :: any(), value :: any(), opts :: Keyword.t()) ::
  t()

Update the "data" of the child with the specified id.

This function is intended to be used to update the current data of a component. The component must have implemented the Scenic.Scene.handle_update/3 callback. All of the built-in components support this.

Unlike put_child, update_child effectively re-initializes the component with the new data. This would be the same data format you have provided when you created the component in the first place. For example, you might update a checkbox component with the value {"New Label", true}

You can specify the same ID to more than one child. This will cause all the components with that id to receive the handle_put call.