rails / request.js

MIT License
389 stars 28 forks source link

how to handle error and redirect? #47

Open laptopmutia opened 2 years ago

laptopmutia commented 2 years ago

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

marcelolx commented 2 years ago

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

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

laptopmutia commented 2 years ago

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

marcelolx commented 2 years ago

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 🤔

laptopmutia commented 2 years ago

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

marcelolx commented 2 years ago

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

brentgreeff commented 2 years ago

@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.

marcelolx commented 2 years ago

@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?

brentgreeff commented 2 years ago

@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.

marcelolx commented 2 years ago

@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.

brentgreeff commented 2 years ago

@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.