request.js icon indicating copy to clipboard operation
request.js copied to clipboard

how to handle error and redirect?

Open laptopmutia opened this issue 2 years ago • 10 comments

any examples?

so I have rails application with turbo and turbo stream then I use stimulus js with request.js to send a patch request but I don't know how to handle the error

import { Controller } from "@hotwired/stimulus"
import onScan from "onscan.js"
import { patch } from "@rails/request.js";
export default class extends Controller {
  static values = {
    sellingId: Number
  }
  connect() {
    let sellingId = this.sellingIdValue
    onScan.attachTo(document, {
      suffixKeyCodes: [13], // enter-key expected at the end of a scan
      onScan: function (sCode, _) { // Alternative to document.addEventListener('scan')
        patch(`/sellings/${sellingId}/add-item`, {
          body: { "selling": { "inventory_id": sCode } },
          contentType: "application/json",
          responseKind: "turbo-stream"
        })
      },
    });
  }
}

here is my controller

  def add_item
    idnya = params.require(:selling).permit(:inventory_id)[:inventory_id]

    if idnya && @selling.update_entry(idnya)
      respond_to do |format|
        format.html { redirect_to selling_path(@selling), notice: "Item was successfully Added." }
        format.turbo_stream { flash.now[:notice] = "Item ditambahkan." }
      end
    else
      flash.now[:error] = "Item tidak ditemukan."
      render :show, status: :unprocessable_entity
    end
  end

I have tried to add this format.turbo_stream { flash.now[:error] = "Item not found." } it could render the flash message just fine

but I felt its wrong since its return 200 ok instead 422 error

laptopmutia avatar Aug 03 '22 10:08 laptopmutia

Hey @laptopmutia,

Request.JS is just a wrapper around the Fetch API, so you should be able to handle errors as you would handle them with the Fetch API. See those examples here to see if it helps

  • https://developer.mozilla.org/en-US/docs/Web/API/fetch#examples
  • https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch (here are some examples with .then & .catch

Fetch API does follow redirects by default, but it won't render the redirected page so what you will have to do very likely is access the response of the redirect and propose a visit with Turbo, something like

patch(`/sellings/${sellingId}/add-item`, {
  body: { "selling": { "inventory_id": sCode } },
  contentType: "application/json",
  responseKind: "turbo-stream"
}).then((res) => {
   if (res.redirected) {
     Turbo.visit(new URL(res.url.toString(), document.baseURI))
   }
})

I haven't tested the code above, but something like that may work. Just have in mind that this will end up making two requests to the redirected location:

1st -> Fetch API because it follows the redirect (if you set redirect: 'manual' then you won't be able to know which is the redirected URL, see https://github.com/whatwg/fetch/issues/763) 2nd -> The Turbo.visit itself will make the request to the redirected location to render the page

marcelolx avatar Aug 03 '22 14:08 marcelolx

I just confused why the turbo stream is not rendered with 422 apparently the default behavior for stream processing is 200 ok

I fix my code wiht this

connect() {
  let sellingId = this.sellingIdValue
  onScan.attachTo(document, {
    suffixKeyCodes: [13], // enter-key expected at the end of a scan
    onScan: async function (sCode, _) { // Alternative to document.addEventListener('scan')
      const response = await patch(`/sellings/${sellingId}/add-item`, {
        body: { "selling": { "inventory_id": sCode } },
        contentType: "application/json",
        responseKind: "turbo-stream"
      })
      if (response.unprocessableEntity) {
        await response.renderTurboStream()
      }
    },
  });

  onScan.setOptions(document, {
    minLength: 1 // change the quantity to 5 for every scan
  });

}

is this code good? I kinda hesitate with the placement of async and await

laptopmutia avatar Aug 03 '22 16:08 laptopmutia

Yeah, Request.JS does only process a turbo stream if the response is ok.

is this code good? I kinda hesitate with the placement of async and await

It is, you should be good with this.

We could improve the docs to mention that the turbo stream will only be processed if the response is ok https://github.com/rails/request.js#turbo-streams, or we could consider processing it always if the response is a Turbo Stream 🤔

marcelolx avatar Aug 03 '22 17:08 marcelolx

I think I prefer the later, but not sure about it since all the example about turbo and turbo-rails never use it, they tend to response the failed request with html page/response

laptopmutia avatar Aug 04 '22 07:08 laptopmutia

Yes, I don't see either why we wouldn't process the turbo stream.

marcelolx avatar Aug 04 '22 13:08 marcelolx

@marcelolx - My code is slightly different.

  const p = await post(to_url, { body: JSON.stringify(params) })

  if (p.redirected) {
    const redirect_url = p.response.url.toString()
     Turbo.visit(new URL(redirect_url, document.baseURI))
  }

Why does turbo do all the magic body-tag swapping if I create a vanilla rails form - but here I need to do the heavy lifting?

How can I tie the response into Turbo's generic behavior.

like

Turbo.magic(response)

Which should handle the redirect - ie 1 request not 2 - and it should behave as normal for turbo-replace etc.

brentgreeff avatar Aug 07 '22 10:08 brentgreeff

@brentgreeff Because it is Turbo that intercepts the form submission, submits it, and then handles the response while Request.JS is only a wrapper around the Fetch API (doing all this magic ).

As far as I know, Turbo does not offer anything like that to handle the response of a request made outside of Turbo.

Couldn't you submit a form and let Turbo intercept the form submission and consequently handle the response of it?

marcelolx avatar Aug 08 '22 11:08 marcelolx

@marcelolx - Yea, I do have examples of that in my code, but this is one of those forms in a table stories - I have 1 form per row. - not sure if it would be valid. - I need to collect values from different cells and collate params to generate my request.

  • maybe I will look at how Turbo works, - as you said, if Turbo is intercepting form submits, - does it also use fetch - there might be code I can extract into a common: Turbo.magic(request) - or I have totally mis-understood how Turbo works - either way I might learn something.

brentgreeff avatar Aug 09 '22 20:08 brentgreeff

@brentgreeff Yeah, I did look yesterday at Turbo and yes, it uses Fetch to make the requests. For some reason, I couldn't understand yet, Turbo is able to access the response of the redirected location and avoid another request to the redirected path but I couldn't figure out how (I did not invest much time, so I probably was missing something).

If you find a solution, please share! We already process turbo streams, so if we could handle redirects (tell turbo to handle it somehow) it definitely would be valuable.

btw, I'll take a look into this again once I have some free time.

marcelolx avatar Aug 09 '22 20:08 marcelolx

@marcelolx - thanks for investing the time. - I guess if people are using Request.js - they want a lot of control - so an explicit handoff to Turbo might be required. - but Turbo should have an interface to allow this. - response could be a redirect - or it could be turbo_stream-tags, or a normal html page.

brentgreeff avatar Aug 12 '22 06:08 brentgreeff