# Best Patterns and Practices for Error Handling?

**URL:** <https://discuss.hotwired.dev/t/best-patterns-and-practices-for-error-handling/3544>\
**Category:** General\
**Created:** [December 3, 2021, 8:51pm UTC](https://discuss.hotwired.dev/t/best-patterns-and-practices-for-error-handling/3544 "2021-12-03T20:51:19Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![corporealfunk](https://avatars.discourse-cdn.com/v4/letter/c/ea5d25/32.png) [@corporealfunk](https://discuss.hotwired.dev/u/corporealfunk)\
**Post date:** [December 3, 2021, 8:51pm UTC](https://discuss.hotwired.dev/t/best-patterns-and-practices-for-error-handling/3544/1 "2021-12-03T20:51:19Z")

</div>

Requesting some community advice on patterns and best practices for error handling when using TurboFrames.

To set the scene, say we look at a very basic “TODO List” app. You can image a Rails controller that pulls all the todos:

```auto
def index
  @todos = Todos.all
end

```

And a view that displays them in the app/views/todos/index.html.erb. This view allows one to mark a TODO as “done” by clicking a “done” button.

```auto
<ul>
  <% @todos.each do |todo| %>
    <%= render partial: 'todo', locals: { todo: todo } %>
  <% end %>
</ul>

```

the app/views/todos/\_todo.html.erb partial:

```auto
<li>
  <%= turbo_frame_tag(dom_id(todo)) do %>
    <%= todo.title %>
    <% if todo.done? %>
      DONE
    <% else %>
      <%= form_with(model: todo, url: complete_todo_path(todo)) do |f| %>
        <%= f.button('Mark Done')
      <% end %>
    <% end %>
  <% end %>
<li>

```

The controller code that responds the form POST (I am not using “respond\_to” blocks here for simplicity):

```auto
def complete
  todo = Todo.find(params[:id])

  todo.update_attribute :done, true

  render partial: 'todo', locals: { todo: todo }
end

```

If all goes well, the partial renders, now with the TODO in the “done” state, Turbo receives that response, sees the turbo\_frame tag with the matching id and renders the response into the frame that exists on the page. Great!

But what about 5xx errors? 404s? Are there patterns or best practices around how we should access and then expose these errors to users? 422s are pretty easy to handle and Turbo can now simply re-render those (for validations, let’s say).

For example, say the above complete controller method throws a 500 for some reason. The user actually sees the TODO disappear and receives no other feedback. Why? Because rails returns a 500 error tracedump in development, and in production serves a generic 500 server error response. Neither of those responses contain a turbo\_frame with the expected dom\_id of the todo that caused the issue. The javascript console tells us this with a thrown error.

I’ve considered several patterns to address this, but none exactly seem “right”:

1. Attach a stimulus controller to the body like this (layout here):

```auto
<body data-controller="errors" data-action="turbo:before-fetch-response->errors#checkStatus">
  <%= yield %>
</body>

```

And the stimulus controller. Obviously you’d probably want to do something other than use an alert box, maybe a nicely styled modal or something, but you get the idea:

```auto
import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
  checkStatus(e) {
    const { status } = e.detail.fetchResponse.response;

    if (status >= 500 && status < 600) {
      alert(`${status} Error. Howdy, the server is bonkers. Try again or reload the page?`);
    }
  }
}

```

This works, but we still have the issue that Turbo removed the turbo\_frame from the page. Granted, since a 500 error occurred, we don’t really know what the state of that todo is in the backend, but, it is also surprising that it disappeared. Perhaps instead of an alertbox, one could use a modal that is simply not closeeable. It is essentially “500 Error, reload the page. There is nothing else you can do. We just don’t know the state of the backend, so refresh it yourself user!”.

(Additionally, in the current state of things, turbo:before-fetch-response is not going to get triggered on, say, NetworkErrors since in that case the fetch has not even happened yet. OK, so we could listen for turbo:submit-start and we should be able to suss out a network error there, but Turbo only fires turbo:submit-start on POST requests. However, With the following pull request: [`GET` Forms: fire `submit-start` and `submit-end` by seanpdoyle · Pull Request #424 · hotwired/turbo · GitHub](https://github.com/hotwired/turbo/pull/424) we’ll be able to also listen to turbo:submit-start for POST and GET requests, so at least we can cover network errors in the above way, just with maybe a new stimulus method and another action on the body.)

1. The above works, but there’s that pesky issue about the todo disappearing. So… what about this in our controller (again, for simplicity I’m not including respond\_to blocks here):

```auto
def complete
  todo = Todo.find(params[:id])

  todo.update_attribute :done, true

  render partial: 'todo', locals: { todo: todo }
rescue ActiveRecord::RecordNotFound
  render turbo_stream: [
    turbo_stream.replace(dom_id(todo), partial: 'todo', locals: { todo: todo }),
    turbo_stream.update('error', partial: 'shared/not_found_error')
  ]
rescue
  render turbo_stream: [
    turbo_stream.replace(dom_id(todo), partial: 'todo', locals: { todo: todo }),
    turbo_stream.update('error', partial: 'shared/server_error')
  ]
end

```

In the above, we are resonding with multiple streams fragments to:

A) Update the todo’s turbo frame with the given todo, so the user will see the state that it is in (instead of it disappearing).  
B) Imagine the layout has a div with id “error” and a Stimulus controller attached to it. Perhaps that stimulus controller can pop a modal or some other UI element appears once the “error” element has new content (which it will once Turbo attaches that stream fragment in the correct place in the DOM).

This (in a way) feels more like “the Hotwire/Turbo way” (if there is a such a thing): everything is rendered on the server.

However, what a pain to catch all the possible errors and respond to them in every controller method in a Turbo-ified application. There’s some fancy footwork that can be done to DRY it out a bit, for sure. For example at the top of our ApplicationController we could use something like this:

```auto
class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :render_404
  rescue_from Exception, with: :render_500

  def render_404(e)
    respond_to do |format|
      format.turbo_stream turbo_stream.update('error', partial: 'shared/not_found_error')
      format.any { raise e }
    end
  end

  def render_500(e)
    respond_to do |format|
      format.turbo_stream turbo_stream.update('error', partial: 'shared/server_error')
      format.any { raise e }
    end
  end
end

```

Now, I haven’t tested that code, but by re-raising e if not a turbo\_stream, I assume the Rails stack will then just handle it as per normal.

Now, this little DRY out doesn’t really address the fact that we are no longer responding with the TurboStream fragment to re-render our TODO, there are ways to figure that out though. We could still rescue in our controllers, and set a controller instance variable like @additional\_error\_streams to an array of streams to by rendered by the render\_404/500 methods, perhaps.

One flaw: What if you’ve got a proxy in your infrastructure and your backend Rails app is down when a request is made? In this case, NGINX or Cloudflare or your frontline server is going to through a “502” bad gateway… and now our client is back in the same boat: Turbo is going to throw a JS error that says the response doesn’t contain the expected turbo\_frame id, the TODO will disappear and the user sees nothing.

So, it seems like some combination of things might be best? What are others doing to maintain a good user experience during 50x/404 errors? I don’t want pieces of the UI to disappear, and I want the user to be notified when things go wrong for any variety of reasons.

---

<div class="post-metadata">

**Author:** ![tleish](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/tleish/32/920_2.png) [@tleish](https://discuss.hotwired.dev/u/tleish)\
**Post date:** [December 4, 2021, 2:50pm UTC](https://discuss.hotwired.dev/t/best-patterns-and-practices-for-error-handling/3544/2 "2021-12-04T14:50:11Z")

</div>

Not an immediate solution, but I suggest you review:

> [@How to deal with 500 in the frame?](https://discuss.hotwired.dev/t/how-to-deal-with-500-in-the-frame/2219/3):
>
> I think this regards to the general question: how to intercept a response in a turbo frame? In my case, I wanted to intercept the 401 unauthorized. This is [my solution](https://discuss.hotwired.dev/t/break-out-of-turbo-frame-on-devise-401-redirect/2479/7).

The discussion in this ticket

> <https://github.com/hotwired/turbo/issues/257>
>
> Currently if the response from turbo requests returns a redirect response whilst… inside a frame, there is no way for the server side to conditionally control what turbo to do with the response, eg. force a full page refresh with target="\_top"
> 
> There are use cases especailly for modal windows where upon succesful save we would want to perform a redirect back to the index page, where as 422 response would do the normal turbo frame replacement within current frame.
> 
> It would be nice to support something like this, which will allow us to control targets from serverside to frame targets by setting frame target via the http response headers.  
> 
> \`\`\`
> if @record.save
> response.headers\["Turbo-Frame"\] = "\_top"
> redirect\_to settings\_locations\_url
> else
> render partial: 'form', status: :unprocessable\_entity
> end
> \`\`\`
> 
> What everyones thoughts are on this? or is there something I am missing to make this easier.

and this PR

> <https://github.com/hotwired/turbo/pull/445>
>
> Closes \[hotwired/turbo#432\]\[\]
> Follow-up to \[hotwired/turbo#94\]\[\]
> Follow-up to …\[hotwired/turbo#31\]\[\]
> 
> When a response from \_within\_ a frame is missing a matching frame, fire
> the \`turbo:frame-missing\` event.
> 
> There is an existing \[contract\]\[\] that dictates a request from within a
> frame stays within a frame.
> 
> However, if an application is interested in reacting to a response
> without a frame, dispatch a \`turbo:frame-missing\` event. The event's
> \`target\` is the \`FrameElement\`, and the \`detail\` contains the
> \`fetchResponse:\` key. Unless it's canceled (by calling
> \`event.preventDefault()\`), Turbo Drive will visit the frame's URL as a
> full-page navigation.
> 
> The event listener is also a good opportunity to change the
> \`\<turbo-frame\>\` element itself to prevent future missing responses.
> 
> For example, if the reason the frame is missing is access (an expired
> session, for example), the call to \`visit()\` can be made with \`{ action:
> "replace" }\` to remove the current page from Turbo's page history.
> 
> \[contract\]: https://github.com/hotwired/turbo/issues/94#issuecomment-756968205
> \[hotwired/turbo#432\]: https://github.com/hotwired/turbo/issues/432
> \[hotwired/turbo#94\]: https://github.com/hotwired/turbo/issues/94
> \[hotwired/turbo#31\]: https://github.com/hotwired/turbo/issues/31
