Help in Phoenix server's reconnect on crash breaking UX

I need help in two things related to Phoenix LiveView.
I’m doing a LiveView page to create Questions to Tests (like math tests, for example).
A question has one wording and one or more alternatives.

I’m using “Quill JS” javascript library to edit data of the question. So, i’ve a screen like this:

First is the question’s wording, second is an alternative answer.


All editors works by events. For example, user change some text of the wording, then frontend emits an event to LiveView module with changes, and then record is updated in database. Or, when LiveView load data of the question’s wording, it emits an event to the frontend, and the browser initializes Quill Js component. Things like this.


Here are some snippets of the code:

@impl true
  def mount(_params, _session, socket) do
    # async loading question record
    send(self(), :load_question)

    socket =
      assign(socket, :question_is_loading, true)
      |> assign(:alternatives_are_loading, true)
      |> assign(:alternatives_count, 0)
      |> assign(:alternatives, [])

    {:ok, socket, temporary_assigns: [alternatives: []]}
  end

@impl true
  def handle_event("editorjs_is_ready", _params, socket) do
    question = socket.assigns.current_question
    socket = push_event(socket, "setup_data_editorjs", Jason.decode!(question.statement))
    {:noreply, socket}
  end

@impl true
def handle_event("editor_save", params, socket) do
    question = socket.assigns.current_question
    json_encoded = Jason.encode!(params)

    {:ok, question} =
      Questions.update_question(question, %{
        statement: json_encoded
      })

    socket = assign(socket, :current_question, question)

    {:noreply, socket}
  end

@impl true
  def handle_info(:load_question, socket) do
    # if it's not created, create a new question
    # in `draft` mode.
    # This question will be persisted and opened until
    # the question is created.
    # If a question in `draft` status created by the user
    # already exists, only load the question and show.

    %User{id: user_id} = socket.assigns.current_user

    socket =
      case Questions.get_draft_question_of_user(user_id) do
        nil -> create_question(socket, user_id)
        question -> assign(socket, :current_question, question)
      end

    socket = assign(socket, :question_is_loading, false)

    question = socket.assigns.current_question

    socket = push_event(socket, "question_statement_ready", Jason.decode!(question.statement))

    # load alternatives
    send(self(), :load_alternatives)

    {:noreply, socket}
  end

Javascript:

fromEvent(editor, "text-change")
            .pipe(skip(1), debounceTime(1500))
            .subscribe((a) => {
              /**
               * Operation to remove unecessary
               * break line
               */
              let delta = editor.getContents();
              let lastOpIndex = delta.ops.length - 1;
              let lastOpInstace = delta.ops[lastOpIndex];

              lastOpInstace.insert = lastOpInstace.insert?.replace(/\n$/, "");

              hook.pushEventTo(selector, "editor_save", delta);
            });

  1. The first question is whether this is the best way to implement this logic ??

Ok, it’s working. But i’m having a problem. I’m using, in the tags that will be used by Quill JS phx-update="ignore", to not participate of change detection, and not reload component all the time.
But, when the server crashes and restart, the page is reloading. It’s a big problem in UX, because if the user is editing when is being restarted, (1) he lost some of the text that he was writing, and the cursor of the mouse leaves the component. Here is an example:

https://media.giphy.com/media/K8Trv2mce6oMbMkP9j/giphy.gif

How to avoid this problem?

Have you figured out why the process is crashing?

“Let it crash” as a philosophy means that unexpected errors shouldn’t take the service down, only restart the process with the last “correct” state, which usually means that you will lose some data stored in the state while it does that, let it crash doesn’t mean that errors should happen all the time and that you should not handle an error that is happening all the time and crashing the process.

So yeah, step 1, figure out why the process is crashing (the server isn’t crashing, only the liveview process).

As for how to avoid, if you can’t handle the errors (you most probably can) and really need to keep the latest state with liveview at all times you will need to store it outside the process with some kind of datastore (for example, redis or ets) and recover it during the mount phase, the focus can be set with Phoenix.Liveview.JS if needed.

1 Like

It is not a specific problem that is causing the crash. I’m purposely causing it to test UX/UI aspects.

Blockquote
let it crash doesn’t mean that errors should happen all the time and that you should not handle an error that is happening all the time and crashing the process

But, this afirmation give some light about the question. In summary, i’ve not to worry too much about crashing affecting UI/UX, correct?

Blockquote
some kind of datastore (for example, redis or ets)

It’s a possibility, in the same way, i’ll have to implement some solution for the “mouse position leaving the component”.


My other question is whether this is the best way to implement a text editor for this context?

It depends on what you mean by crashing, if the liveview process (or its supervisor) crashes you lose the state and the system will try to remount the state with the available info, which will probably result in some lost data, but if a single user’s liveview process crashes it should not affect other users in the system.