remix icon indicating copy to clipboard operation
remix copied to clipboard

Meta function can't access `loader` data when it throws

Open janhesters opened this issue 2 years ago • 6 comments

Context

Why is this important? Screen reader users often rely on title's to infer the content of a page. And the loader is used to dynamically set the title property in meta functions.

The inability to set title's dynamically on errors severely hinders this functionality.

What version of Remix are you using?

1.6.7

Steps to Reproduce

You need a loader that throws.

import type { MetaFunction } from '@remix-run/node';
import { json } from '@remix-run/node';
import { Outlet, useCatch } from '@remix-run/react';

export const loader = () => {
  if (Math.random() > 0.5) {
    throw json(
      { message: 'not found', slug: 'foo', title: 'yolo' },
      { status: 400 },
    );
  }

  return json({ title: 'Not throwing' });
};

export const meta: MetaFunction = parameters => {
  console.log(parameters);
  return {};
};

export default function MyComponent() {
  return <p>Hello</>;
}

export function CatchBoundary() {
  const caught = useCatch();

  return (
    <p>
      {caught.status}: {caught.data.slug}
    </p>
  );
}

Expected Behavior

The meta function should have access to the data of json that was thrown.

Actual Behavior

The meta function doesn't see the loader's data.

Possible Alternative

In case of errors you can set the title in the higher routes that didn't throw, but that would require all routes always getting all titles, which is bad for performance 😕

Or maybe there is another way to set the title in CatchBoundarys?

janhesters avatar Aug 02 '22 11:08 janhesters

If you throw a response, it's not valid data from your loader, hence loader data is not present in your meta function.

If you want to update the title from your <CatchBoundary/> you can add a useEffect

export function CatchBoundary() {
  const caught = useCatch()
  useEffect(() => {
    if (caught?.data?.title) {
      document.title = caught.data.title
    }
  })
  return (
    <>
      <h1>CatchBoundary</h1>
      <p>Title should be {caught.data.title}</p>
      <p>
        {caught.status}: {caught.data.slug}
      </p>
    </>
  )
}

https://codesandbox.io/s/remix-catchboundary-title-h2nske?file=/app/routes/index.tsx

kiliman avatar Aug 02 '22 15:08 kiliman

@kiliman Thank you for this!

If you throw a response, it's not valid data from your loader, hence loader data is not present in your meta function.

Hmm, I don't get that. Why would we assume that? Wouldn't it make more sense to add some conditionals into the meta function so it can handle happy as well as sad paths?

If you want to update the title from your <CatchBoundary/> you can add a useEffect

This definitely solves the issue, but seems like a hacky solution considering the Remix convention is to do it with meta 🤔

janhesters avatar Aug 02 '22 20:08 janhesters

When you throw from a function, you don't get the same result as if the function returned data. The meta function currently receives the returned loader data.

I'm not saying this can't be changed. Just explaining how it currently works today.

I guess my only concern is that if this was changed, would it be a breaking change? Since meta doesn't expect thrown data, it may not be in the same shape as loader data, so existing meta functions could break if they don't check. I think the only way it would work is if they simply added another property to the meta arguments. Something like catchData. Then existing code will work and new code can inspect that property as needed.

@ryanflorence I read in the decision doc for remix router/react-router that there may be no distinction between Error and Catch boundaries in the future. Have you considered adding the error and catch data to the meta function? This way developers can have more control of the UI being generated in cases of non-happy paths.

kiliman avatar Aug 03 '22 14:08 kiliman

@janhesters I ran across this issue as well, and settled on another admittedly hacky solution, albeit one that renders on the server-side. Here's a simplified version of my root component using a module-level closure to determine its meta title.

app/root.tsx
import type { MetaFunction } from "@remix-run/node";
import { Meta, Outlet, useCatch } from "@remix-run/react";

let metaTitle = "Initial title";

export const meta: MetaFunction = () => {
  return {
    title: metaTitle,
  };
};

function Document(props: { children: React.ReactNode }) {
  return (
    <html>
      <head>
        <Meta />
      </head>
      <body>{props.children}</body>
    </html>
  );
}

export default function Root() {
  return (
    <Document>
      <Outlet />
    </Document>
  );
}

export function CatchBoundary() {
  const caught = useCatch();

  metaTitle = `${caught.status}: ${caught.statusText}`

  return (
    <Document>
      <p>{caught.data}</p>
    </Document>
  );
}
app/routes/index.tsx
import type { LoaderFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

export const loader: LoaderFunction = ({ request }) => {
  if (request.url.match(/[?|&]oops=/)) {
    throw new Response("Oops!", { status: 400, statusText: "Bad request" });
  }
  return new Response("Success!");
};

export default function IndexRoute() {
  const content = useLoaderData<string>();
  return <p>{content}</p>;
}

Navigate to http:/localhost:3000/ and the initial title is rendered. Navigate to http:/localhost:3000/?oops and the title assigned within the CatchBoundary is rendered.

On the face of it, passing error/catch data to a MetaFunction would come in handy

bencallaway avatar Sep 09 '22 12:09 bencallaway

@chaance is this handled by the new Meta API? 🙏🏼

machour avatar Jan 22 '23 15:01 machour

@chaance is this handled by the new Meta API? 🙏🏼

No. We're aware of this limitation. I'll make sure something is on our roadmap if it isn't already.

chaance avatar Jan 22 '23 22:01 chaance

🤖 Hello there,

We just published version v0.0.0-nightly-1d3d86e-20230817 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

github-actions[bot] avatar Aug 17 '23 07:08 github-actions[bot]

🤖 Hello there,

We just published version 2.0.0-pre.0 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

github-actions[bot] avatar Aug 28 '23 22:08 github-actions[bot]