prismicio / prismic-react

React components and hooks to fetch and present Prismic content
https://prismic.io/docs/technologies/homepage-reactjs
Apache License 2.0
154 stars 40 forks source link

Add `usePrismicLink` as an alternative for `PrismicLink` #145

Closed Klynger closed 2 years ago

Klynger commented 2 years ago

Is your feature request related to a problem? Please describe.

PrismicLink doesn't work very well on Typescript. Given the complexity of the type, you can't even pass a className to the component without having to disable Typescript. But at the same time, it is a handy component. So it would be nice to have a way to re-use its logic.

Describe the solution you'd like

We could have a hook like const anchorProps = usePrismicLink({ field, document, href, ... })

Describe alternatives you've considered

Additional context

Even if we find a way to fix the PrismicLInk type, I think that we should have a hook with the logic of this component

angeloashmore commented 2 years ago

Hi @Klynger, thanks for bringing this up.

There was a regression introduced in v2.1.2 that removed default support for common props like className, onClick, etc.

While the change technically is more correct (<PrismicLink> can render any component, including ones that don't support className or onClick), it is not practically usable.

I see where you're going with suggesting a hook since it gives you the PrismicLink-specific props needed to pass to your own component. That being said, I don't think a hook is the best solution for this issue. Assuming we fix the type issue and now have <PrismicLink> and usePrismicLink(), the hook would almost always be less ergonomic. You effectively would need to use the hook and then manually pass that data to some other component.

If I'm misunderstanding the request, please let me know. 🙂

Here is what I think we can do to remedy this issue:

  1. Restore the default props for <a>. This includes props like className, onClick, and anything provided by React.AnchorHTMLAttributes. This would solve type issues for most users.
  2. Provide a convention for overriding the default behavior for uncommon use cases. If <PrismicLink> is configured to use something that works differently than <a> (for example, it does not accept className), there should be a standard way to override it.

1 will be included in a future patch version.

2 could be addressed in three ways. In all three ways, you would use a custom <PrismicLink> implementation built off <PrismicLink> provided by @prismicio/react.

Rather than import <PrismicLink> from @prismicio/react, you would import your custom component instead.

1. Custom <PrismicLink> using global configuration and TypeScript 4.7 Instantiation Expressions

(Note: This assumes PrismicLink is configured globally via <PrismicProvider> to render <Link> for internal links.)

You can take advantage of TypeScript 4.7's Instantiation Expressions feature to define a custom instance of <PrismicLink> specific to your internal and external link React components.

react-router-dom's <Link> is used as an example below, but this could be any component.

// src/components/PrismicLink.tsx

import { PrismicLink as PrismicLinkBase } from "@prismicio/react";
import { Link } from "react-router-dom";

export const PrismicLink = PrismicLinkBase<typeof Link>;

TypeScript 4.7 is currently in beta, and we cannot expect users to always be using the latest version of TypeScript. As such, this solution will not apply to everyone.

2. Custom <PrismicLink> using global configuration and type parameters in JSX

(Note: This assumes PrismicLink is configured globally via <PrismicProvider> to render <Link> for internal links.)

Similar to the above solution, a custom instance of <PrismicLink> can be created with the correct generic. This does not rely on bleeding-edge TypeScript features.

// src/components/PrismicLink.tsx

import {
    PrismicLink as PrismicLinkBase,
    PrismicLinkProps,
} from "@prismicio/react";
import { Link } from "react-router-dom";

export const PrismicLink = (props: PrismicLinkProps<typeof Link>) => (
    <PrismicLinkBase<typeof Link> {...props} />
);

3. Custom <PrismicLink> using non-global configuration

(Note: This assumes PrismicLink is not configured globally via <PrismicProvider> to render <Link> for internal links.)

Alternatively, a custom PrismicLink instance could be configured by passing the internal/external React components directly. This ignores any global configuration provided to <PrismicProvider>.

// src/components/PrismicLink.tsx

import {
    PrismicLink as PrismicLinkBase,
    PrismicLinkProps,
} from "@prismicio/react";
import { Link } from "react-router-dom";

export const PrismicLink = (props: PrismicLinkProps<typeof Link>) => (
    <PrismicLinkBase internalComponent={Link} {...props} />
);

If you have any questions or suggestions, please feel free to reply here. Thanks! 🙂

angeloashmore commented 2 years ago

I'm going to close this issue since we won't be adding a usePrismicLink() hook at this time. I recommend using one of the three strategies shown above when using TypeScript with a link component that diverges from <a>'s inherit props.

If anyone feels strongly about creating a usePrismicLink() hook here, please feel free to comment here and we can discuss further. Thanks!