denoland / fresh

The next-gen web framework.
https://fresh.deno.dev
MIT License
12.05k stars 607 forks source link

Inside `f-partial-nav` the server-side redirects do not work. #2560

Open Sleepful opened 6 days ago

Sleepful commented 6 days ago

lets say I have:

  <Partial name="user-feedback">
  </Partial>
  <form
    method="post"
    class="flex flex-col space-y-2"
    f-partial="/routes/submit"
  >

Then in the submit route:

// routes/submit.tsx
...
  async POST(req, ctx) {
    if (something) {
      return ctx.render(); // some partial here
    }
    else {
        const headers = new Headers();
        headers.set("location", "/another-place");
        return new Response(null, {
          status: 301,
          headers,
        });
    }

...
export default function Page() {
  return (
    <Partial name="user_feedback">
        Invalid input!
    ....
}

The problem here is that the 301 redirect does not render in the browser because the browser is expecting a Partial like the one in Page(), but the /another-place route is not a Partial's route, it should be full reload.


I imagine that the client never knows about the 301, it only sees the final result (HTML) from the /another-place resource, and the client treats it as the originally requested resource, so it tries to search for partials in that resource, which there are none in it so nothing "happens" even though it returned an entirely different webpage.


Side-note, somewhat amusing, if an a anchor tag is outside of f-client-nav then it has no issue with redirects, even if there are Partials on the page. However, if an a anchor ag is inside f-client-nav, it cannot handle server-side redirects, even if it has not been enabled with an f-partial="/partials/abc" attribute. I think it would be worth-noting in the docs, although with better phrasing than mine, my pedagogy isn't good right now.

Sleepful commented 6 days ago

A way around the issue is to simply use client-side redirect by rendering an island that uses window.location.href = "location";, note that the island needs to be rendered from the partial route and it needs to be wrapped with the Partial JSX:

// routes/submit.tsx

import { Redirect } from "#islands/Redirect.tsx";

...
  async POST(req, ctx) {
    if (something) {
      return ctx.render({redirect: false}); 
    }
    else {
        return ctx.render({redirect: true});
    }

...
export default function Page(props) {
  const { redirect } = props.data
  if (redirect) {
    return (
      <Partial name="user_feedback">
        <Redirect location="/another-place" />
      </Partial>
    );
  } else {
    return (    
        <Partial name="user_feedback">
            Invalid input!
    ....
}
// #islands/Redirect.tsx
import { useEffect } from "preact/hooks";

interface Props {
  location: string;
}

export function Redirect(props: Props) {
  useEffect(() => {
    window.location.href = props.location;
  });
  return <div></div>;
}

This ... works. But it removes some of the benefit of code organization that Partials allows.