remix icon indicating copy to clipboard operation
remix copied to clipboard

docs: explain why links can't access data

Open buzinas opened this issue 3 years ago • 1 comments

What version of Remix are you using?

1.7.0

Steps to Reproduce

Currently the MetaFunction is described like this:

export interface MetaFunction<Loader extends LoaderFunction | unknown = unknown, ParentsLoaders extends Record<string, LoaderFunction> = {}> {
    (args: {
        data: Loader extends LoaderFunction ? SerializeFrom<Loader> : AppData;
        parentsData: {
            [k in keyof ParentsLoaders]: SerializeFrom<ParentsLoaders[k]>;
        } & RouteData;
        params: Params;
        location: Location;
    }): HtmlMetaDescriptor;
}

While the LinksFunction is described like this:

export interface LinksFunction {
    (): LinkDescriptor[];
}

Expected Behavior

I'd like to access data when exporting my links function in order to grab some info, eg:

export const links: LinksFunction<typeof loader> = ({ data }) => {
  const linkArr: LinkDescriptor[] = []
  
  if (data.pagination.page > 1) {
    linkArr.push({ rel: 'prev', href: `/page/${data.pagination.page - 1}` })
  }
  if (data.pagination.page < data.pagination.pages) {
    linkArr.push({ rel: 'next', href: `/page/${data.pagination.page + 1}` })
  }
  
  return linkArr
}

Actual Behavior

I can't access data, thus I can't set up links that depend on them:

export const links: LinksFunction<typeof loader> = ({ data }) => {
  const linkArr: LinkDescriptor[] = []
  
  if (data.pagination.page > 1) {
    linkArr.push({ rel: 'prev', href: `/page/${data.pagination.page - 1}` })
  }
  if (data.pagination.page < data.pagination.pages) {
    linkArr.push({ rel: 'next', href: `/page/${data.pagination.page + 1}` })
  }
  
  return linkArr
}

For reference: https://developers.google.com/search/blog/2011/09/pagination-with-relnext-and-relprev

buzinas avatar Aug 29 '22 07:08 buzinas

links cannot access data, because if it did, it would introduce a waterfall where styles can't be loaded until after data resolves. Remix needs links to be "static" in order to do its thing, and therefore, it cannot depend on data.

For your use-case, you can use https://github.com/sergiodxa/remix-utils#dynamiclinks which internally uses handle().

This should definitely be explained in the documentation, so labeling this issue as a documentation issue.

machour avatar Aug 29 '22 09:08 machour

@machour see you changed the issue title from "Access to data/location on LinksFunction" and while I agree with the data portion of your response, what about location? Could remix pass the location to the links function?

tom-sherman avatar Feb 28 '23 17:02 tom-sherman

@tom-sherman looks like you can do this using the meta function if you update to the V2 conventions and use matches and/or location which are supplied

https://remix.run/docs/en/main/pages/v2#updating-to-the-new-meta

image

rub1e avatar Jun 21 '23 12:06 rub1e

@tom-sherman I know this is almost four months later, but like @rub1e says, you can get the current route info from matches:

export function meta({ matches }: V2_MetaArgs) {
  const leaf = matches.at(-1);
  const { page } = leaf!.params;
  const pageNum = Number(page);
  const prev = pageNum > 1 ? pageNum - 1 : null;
  const next = pageNum < 10 ? pageNum + 1 : null;

  // Remix will ignore non-truthy entries
  return [
    prev ? { tagName: "link", rel: "prev", href: `/page/${prev}` } : null,
    next ? { tagName: "link", rel: "next", href: `/page/${next}` } : null,
  ];
}
image

kiliman avatar Jun 21 '23 17:06 kiliman