LiveComponent without local state forces apps to use more javascript

Hi, I am developing a template creator with Elixir and Phoenix, it is drag and drop app and provides many predefined UI components like MegaMenu or Tabs etc. (based on https://flowbite.com).
I have a big problem that forces me to use Phoenix LiveView Hook and connect to JS client side to keep n state.
A real world and a complex UI that has nested component inside the other component, if I want to create a state which is locally I should create a state inside its parent!!.

For example:
Imagine to create a text field to take tags from a user: Sport, News, Elixir and etc.

And after that if your user click to send it should be send to the LiveView parent module, but before that where you can save these tags inside a state?

This component maybe used inside 2 the other components.

LiveViewModule --> Header Component --> SearchBox --> TagComponent.

It is a very simple requirement you see if you want to create a state inside LiveViewModule you should pass it to 2 another components.

The only way now, I can do this create a Hook and send to client javascript file and after that I send it back to my component

For example:

this.pushEventTo(component_id_of_myself, 'save', {
  tags: ["tag1", "tag2"]
});

Other problem

I want to create a UI package like the packages in NextJS, Or react and I want to reduce my client side JS and use Phoenix LiveView instead of. with this non-existing local state in liveComponent I should tell my user install the JS package too

Just for seeing there are many UI component in my project that I need to save local state for them.

If you have any suggestion please let me know, because I do not want to create another LiveView module and call it something like this:

<%= live_render(@conn, SomeComponent, id: :some_conponent) %>

Thank you

3 Likes

I don’t entirely follow. Why can’t you store the state in the live component?

In your example, the state is some configuration, not tags. Are the tags the user selects inside the config object?

1 Like

Sorry, I just wanted to give an example of sending and receiving responses, so I edited the sample code.
It should be noted I change the DOM with Javascript Hook to show Tags in bottom of text field for example.

This is a simple example, I need this in another components

Are you aware of phx-target? You should be able to have any of the front end events sent to any live component you want.

What sample code? Can you show a complete example of the issue you are referring to?

I’m sorry, I don’t understand what my need for the local state (like ReactJS useState) inside the component has to do with phx-target. I am using phx-target in my component many times especially wen I want to use my LiveComponent events instead of parent events

ِYou can see I create a text field with selection darkmod, lg size and etc, so I do not want to create many state inside a LiveView Module as parent because it is just for my component.

for example see this lines:

For calling:


I need to have a local state inside my JS stateLessConfig to prevent creating a state in mount function.

I hope explain what I need.


Another example.

Imagine you are building a map component that allows the user to click on the map, leave a comment, and finally hit the submit button.
This map component is used in 4 layers inside the other component. If you want to send the state from the live module itself, you must give it to 4 components as input so that it reaches the map component at the end. If this state is only for the map component and when the user clicks the send button, it will be sent to the live module.

With the current facilities, we have to put all the states in the Live module, and the component cannot have an independent state to edit it with its own events.

Thank you in advance

LiveComponents can have their own independent state. That’s what differentiates a stateful Phoenix.LiveComponent from a stateless functional Phoenix.Component.

LiveComponents are a mechanism to compartmentalize state, markup, and events in LiveView.

LiveComponents are defined by using Phoenix.LiveComponent and are used by calling Phoenix.Component.live_component/1 in a parent LiveView. They run inside the LiveView process but have their own state and life-cycle. For this reason, they are also often called “stateful components”. This is a contrast to Phoenix.Component, also known as “function components”, which are stateless and can only compartmentalize markup.
source: Phoenix.LiveComponent — Phoenix LiveView v0.20.2

For example, the map component that is nested inside the LiveView could be a LiveComponent able to manage its own state, for example comments left by the user. You can see this pattern in the form LiveComponent created by the Phoenix generators via mix phx.gen.live X where the form state is kept within the LiveComponent rather than the parent LiveView.

# assuming `mix phx.gen.live Accounts User users name:string age:integer`

defmodule MyAppWeb.UserLive.FormComponent do
  def render(assigns) do
    ~H"""
      <.simple_form
        ...
        phx-target={@myself}
        phx-change="validate"
      >
      ...
    """
  end
  
  def update(%{user: user} = assigns, socket) do
    changeset = Accounts.change_collection(user)

    {:ok,
     socket
     |> assign(assigns)
     |> assign_form(changeset)} # set up independent form state
  end

  def handle_event("validate", %{"user" => user_params}, socket) do
    changeset =
      socket.assigns.user
      |> Accounts.change_user(user_params)
      |> Map.put(:action, :validate)

    {:noreply, assign_form(socket, changeset)} # update independent form state
  end

  defp assign_form(socket, %Ecto.Changeset{} = changeset) do
    assign(socket, :form, to_form(changeset)) # form state local to LiveComponent
  end
end

Now if by local state, you’re specifically referring to client side state as opposed to server side state, then that’s where something outside of LiveView such as AlpineJS might be useful.

1 Like