Closed angeloashmore closed 2 years ago
Awesome! š
I just have few comments regarding RichText
and RichTextAsText
:
Prismic
? I feel like RichText
is a common concept like Link
, maybe for consistency and clarity it's better to keep the prefix (SliceZone
making an exception because it's a Prismic concept?)RichTextAsText
, have we considered just having an asText
boolean prop on RichText
?
<RichText field={doc.text} /> // Formatted
I feel like this also kinda help with the mental modal of:
- link field > link component
- richtext field > richtext component
On a side note just had a look at the imgix integration for React, it's cooler than Vue's haha (gotcha with Vue's being that it wants you to pass just the image path, making it [not convenient to be used with Prismic's images](https://twitter.com/li_hbr/status/1309127963573727235))
Cheers!
Lots of good ideas. I'll take a second read next week and give a proper opinion
Will the RichText component still accept an HTML Serializer?
By giving access to arguments, the HTML Serializer can help address edge cases. Beyond that, for advanced implementations, some users supply a higher-order function that returns an HTML Serializer to the HTML Serializer prop.
@lihbr
RichText & RichTextAsText: Why not prefixing them with Prismic? I feel like RichText is a common concept like Link, maybe for consistency and clarity it's better to keep the prefix (SliceZone making an exception because it's a Prismic concept?)
I figured RichText is a Prismic concept. PrismicLink and PrismicToolbar need to be prefixed because Link and Toolbar are common, but RichText isn't.
If RichText is considered common, then yes, we should name it <PrismicRichText>
and <PrismicRichTextAsText>
(The AsText version is quite a lot to type now - see below).
I kinda feel weird about RichTextAsText, have we considered just having an asText boolean prop on RichText?
Yes, using different components here was intentional. A boolean prop (or an enum prop like mode="text"
or mode="richText"
) could lead to a messy API. If mode="text"
is provided for example, linkResolver
and components
are no longer relevant to the component. Do we type those as never
now, causing a type error? Or do we accept them, but also ignore them in the implementation?
Separating Rich Text and Text into separate components keeps each component more focused. Using <Text>
in itself takes the place of providing a boolean/enum prop. In other words, using the <Text>
component already declares to the reader/developer that we will be rendering this RichText field as text. A prop would serve the same purpose, but with the added weirdness described above.
If we decide to prefix the Rich Text components with "Prismic", we could rename the components to be more straightforward:
PrismicRichText
PrismicText
(without the prefix, I figured a component named Text
was too common, and PrismicText
alongside RichText
was confusing, thus RichTextAsText
was selected).@samlfair
Will the RichText component still accept an HTML Serializer?
Yes, accepting an HTML serializer should still be included. At the very least, it is for compatibility with existing codebases.
Could you give examples of edge cases that could only be handled via an HTML serializer function? I think for most cases, if not all, the components
prop can handle everything if the correct data is provided to each component. If we can find a case where it cannot handle it, then htmlSerializer
should continue being a first-class prop.
I figured RichText is a Prismic concept. PrismicLink and PrismicToolbar need to be prefixed because Link and Toolbar are common, but RichText isn't.
If RichText is considered common, then yes, we should name it
<PrismicRichText>
and<PrismicRichTextAsText>
(The AsText version is quite a lot to type now - see below).
Ok, to me RichText is definitely a common concept, not a Prismic one (+ Google for ref. https://www.google.com/search?q=richtext)
Yes, using different components here was intentional. A boolean prop (or an enum prop like
mode="text"
ormode="richText"
) could lead to a messy API. Ifmode="text"
is provided for example,linkResolver
andcomponents
are no longer relevant to the component. Do we type those asnever
now, causing a type error? Or do we accept them, but also ignore them in the implementation?Separating Rich Text and Text into separate components keeps each component more focused. Using
<Text>
in itself takes the place of providing a boolean/enum prop. In other words, using the<Text>
component already declares to the reader/developer that we will be rendering this RichText field as text. A prop would serve the same purpose, but with the added weirdness described above.
Ok, makes sense! I'm not familiar with React standards that much
If we decide to prefix the Rich Text components with "Prismic", we could rename the components to be more straightforward:
PrismicRichText
PrismicText
(without the prefix, I figured a component namedText
was too common, andPrismicText
alongsideRichText
was confusing, thusRichTextAsText
was selected).
I like how this sounds! <PrismicRichText />
and <PrismicText />
Could you give examples of edge cases that could only be handled via an HTML serializer function? I think for most cases, if not all, the
components
prop can handle everything if the correct data is provided to each component. If we can find a case where it cannot handle it, thenhtmlSerializer
should continue being a first-class prop.
@angeloashmore Currently, the HTML Serializer receives four arguments: type, element, content, children. Will the props
argument in the component
prop provide access to all of that data? If so, then there's no issue.
Otherwise, here's an edge case where we use a higher-order function for the HTML Serializer (admittedly, this is pretty obscure):
https://community.prismic.io/t/htmlserializer-grouped-images/2863
This all looks awesome! The only suggestion I have is give the ability to choose how to join the Rich Text blocks in the < RichTextAsText />
element. Joining them with a space is probably what you need 99% of the time, but I have run into a couple instances where I've needed to join them with a new line instead.
Maybe all we need is a prop to specify that you want to join with a new line instead of a space. I'm not sure if there are any other use-cases.
@lihbr
Cool! Let's go with <PrismicRichText>
and <PrismicText>
then.
@samlfair
The props
argument may be an HTML-oriented version of the standard Rich Text serializer arguments. A link, for example, may receive the following props
object:
type Props = {
href: string
target?: string
rel?: string
children: React.ReactNode
}
This is done to have greater compatibility with existing, non-Prismic-specific components. We could add a fieldData
or prismic
prop to all components in the components
prop that contains the lower-level type
, element
, content
, and children
values.
type Props = {
href: string
target: string
rel: string
children: React.ReactNode
prismic: {
type: string
element: Record<string, unknown>
content?: React.ReactNode
children: React.ReactNode
}
}
That linked serializer example is pretty obscure, but shows there are cases where a function may be necessary. We can continue accepting an serializer function in these cases while recommending the object form whenever possible. We should be able to detect if a user provides a function or an object automatically.
@levimykel
Good catch! Yes, we can accept a separator
prop to handle that case while defaulting to a space.
Hey, some quick feedback I have on the refresh of the React kit after working on the refresh of the Vue kit:
I'm unsure how relevant it is to the React ecosystem but have we considered allowing the Prismic provider to support an alternative endpoint
/clientConfig
option pair to the client
one in order to allow a single package init of the kit? (in order to allow users to skip the install of @prismicio/client
):
- import * as prismic from '@prismicio/client'
import { PrismicProvider, PrismicLink } from '@prismicio/react'
import { Link } from 'react-router-dom'
import { linkResolver } from '../linkResolver'
import { Heading } from './Heading'
- const endpoint = prismic.getEndpoint('qwerty')
- const client = prismic.createClient(endpoint)
const richTextComponents = {
h1: 'h2',
h2: Heading,
h3: (props) => <Heading as="h4" {...props} />,
}
const App = ({ children }) => {
return (
<PrismicProvider
- client={client}
+ endpoint={"my-repo"}
+ clientConfig={{ accessToken: "xxx" }}
linkResolver={linkResolver}
richTextComponents={richTextComponents}
internalLinkComponent={Link}
>
{children}
</PrismicProvider>
)
}
The client instance could then be configured with a lazy-loaded ponyfill of a simple fetch implementation to allow seamless basic usage through SSR too.
Of course, this option would be purely additional and doesn't remove the ability to configure the provider using the client
way.
My main doubt about it is that I can think of it making less sense in frameworks like Next.js where you might need to have a client instance of your own anyway for usage with getStaticProps
& cie, but just wanted to suggest š¤
For reference this is something that got implemented on the new Vue kit as I couldn't think of case's like Next.js' but maybe there are cases in React where that would benefit users, maybe Vite + React, or craco
š¤·āāļø
<prismic-link />
to handle documentsYou can easily end up in that scenario:
/blog
and want to list all your blog posts;Options A: You use the routes resolver option of the API and know that the link will be available at document.url
but using the routes resolver is not always possible, yet it is a bit advanced to retrieve the URL manually in the document head;
Options B: You use the new helper from @prismicio/helpers
: documentAsLink()
and resolve each link on the template. While this work well it feels a bit like you're on your own;
Options C: The <prismic-link />
component also handles documents so that this become possible:
<PrismicLink field={document} >
{/* My beautiful blog post card */}
</PrismicLink>
It's fairly straightforward to discerns a document from a field (example)so I think this could be a nice addition š¤
Cheers!
I'm conflicted here. I see the benefit of providing just client config w/ a default fetcher, but doing so directly connects the React library to the client library in a way that makes them not modular. And although we can allow users to provide a client directly, having more than one way to do the same thing could ultimately result in more confusion.
You make a good point about Next users as well - they need to use the client directly. They also would not be using any of the React library's client methods since any client work should be done in the SSR functions (getStaticProps
, getStaticPaths
).
I think I prefer the idea of providing a client instance rather than configuration for the client. It teaches users how to use the client library which will apply to any JavaScript project. If someone exports a client in a client.js
file, it can be used in custom scripts, the React library, etc.
The client could also not be provided at all if the user does not use the client hooks. (The same would apply if we accepted the config as props - they would also be optional).
The ergonomic difference is minimal as demonstrated in your example:
- import * as prismic from '@prismicio/client'
import { PrismicProvider, PrismicLink } from '@prismicio/react'
import { Link } from 'react-router-dom'
import { linkResolver } from '../linkResolver'
import { Heading } from './Heading'
- const endpoint = prismic.getEndpoint('qwerty')
- const client = prismic.createClient(endpoint)
const richTextComponents = {
h1: 'h2',
h2: Heading,
h3: (props) => <Heading as="h4" {...props} />,
}
const App = ({ children }) => {
return (
<PrismicProvider
- client={client}
+ endpoint={"my-repo"}
+ clientConfig={{ accessToken: "xxx" }}
linkResolver={linkResolver}
richTextComponents={richTextComponents}
internalLinkComponent={Link}
>
{children}
</PrismicProvider>
)
}
<PrismicLink />
to handle documentsThis is a good idea! The component already accepts a field
prop for link fields. For documents, I think it makes more sense to add an additional document
prop for full documents. The component should only accept field
or document
(reflected in the types).
Awesome, checks my concerns about the built-in client! Nice for links~
@lihbr document
is now a prop on <PrismicLink>
š (https://github.com/prismicio/prismic-reactjs/commit/0768280e049d120cb93ebac581bc4e629ff98c67)
Just found this and it looks like a great potential step forward.
To concur with @angeloashmore, as a Next user, I'd prefer the client be independent of the Provider, though I can see where that could be useful for non SSR/SSG framework users. Would it make any sense to allow both props and if both are provided, use clientConfig over client?
Hey @riceboyler, we're going to leave the client
prop as is and not introduce a clientConfig
option for now. This is still open for discussion if there's a good reason to support a clientConfig
prop.
@prismicio/react@v2.0.0
was just published (see #97) so I'm going to close this issue.
Thanks for your feedback, everyone! š
Overview
This request for comments (RFC) presents a collection of React components and hooks to make presenting and fetching Prismic content easy and extendable. Some of the described components are updates to the existing
prismic-reactjs
library, while others are new.These ideas have been developed as a result of real-world use in different environments including within agencies and in-house development teams.
The proposals defined in this RFC are designed for all React users. Some are general enough to be used in frameworks (such as Next.js and Gatsby), while others are recommended for non-framework use. Each section includes a "Designed to be used with" label to identify for whom it is designed.
Background Information
prismic-reactjs
currently includes the following exports:Components
<RichText>
: Render a Rich Text field into React ComponentsFunctions
RichText.asText()
: Convert a Rich Text field into a text stringDate()
: Parse a Date field into a Date objectLink.url()
: Convert a Link field into a URLThis API combines a React Component with a collection of helper functions from
@prismicio/helpers
and@prismicio/richtext
. The combination of components and functions leads to non-idiomatic React code, mixing JSX (e.g.<RichText>
) with JavaScript expressions (e.g.{RichText.asText()}
).Components provide a way to encapsulate functionality. The current library does not fully utilize this concept.
Link.url()
, for example, could be integrated into a<Link>
component which performs the URL transformation implicitly. BecauseLink.url()
is simply a re-exported helper from@prismicio/helpers
, users could import that library instead if such lower-level functionality is needed.Proposal
The following components and hooks make for a more robust React integration with Prismic.
<PrismicProvider>
: Central configuration.<SliceZone>
: Render Slices in a Slice Zone.<RichText>
: Render a Rich Text field into React Components.<RichTextAsText>
: Render a Rich Text field as text.<PrismicLink>
: Render a Link field as a router-specific Link or anchor element.<PrismicToolbar>
: Add the Prismic Toolbar script.usePrismicDocument()
, et al.: Prismic document fetching hooks.Each component and hook is described below.
Rename to
@prismicio/react
To mirror recent efforts to streamline Prismic's library naming, the library should be renamed from
prismic-reactjs
to@prismicio/react
.The following Prismic libraries also follow this convention:
@prismicio/client
@prismicio/helpers
@prismicio/richtext
@prismicio/types
<PrismicProvider>
Designed to be used with: React w/o a framework, Next.js, Gatsby
This Context Provider sets up app-wide configuration for the library's components and hooks. It can contain an app's
@prismicio/client
instance, Link Resolver, Rich Text components, and a router-specific Link component. See the following component and hook descriptions to learn how they are used.Using the provider is optional. If it is not used, components and hooks can be configured where they are used.
In cases where multiple repositories are used to gather content, or when location-specific overrides are needed, values provided to the provider have a lower priority than configuration provided to a component or hook directly.
<SliceZone>
Designed to be used with: React w/o a framework, Next.js, Gatsby
See the dedicated
<SliceZone>
RFC: https://github.com/prismicio/prismic-reactjs/issues/86<RichText>
Designed to be used with: React w/o a framework, Next.js, Gatsby
This component renders an HTML representation of a Rich Text field. It automatically converts links using a provided Link Resolver. By default, it renders a standard set of HTML elements (e.g. Heading 1 becomes
<h1>
), but can be overridden using a list of components.The component to be rendered can be provided in several ways:
"h2"
).Heading
).(props) => <Heading as="h4" {...props} />
)This provides users an easy and flexible way to override the rendered components. This replaces the HTML Serializer concept with a simpler approach without removing capabilities.
If
<PrismicProvider>
is used and configured with arichTextComponents
prop, it will be used as<RichText>
'scomponents
prop. If acomponents
prop is provided to<RichText>
, it will take priority over the prop provided to<PrismicProvider>
. The same behavior is included forlinkResolver
.<RichTextAsText>
Designed to be used with: React w/o a framework, Next.js, Gatsby
This component renders a text representation of a Rich Text field. It serves as the "non-rich" counterpart to the
<RichText>
component as it strips all formatting and HTML representations within the field's contents.Since the output is a string, no customization aside from the
field
prop is necessary.<PrismicLink>
Designed to be used with: React w/o a framework, Next.js, Gatsby
This component renders a link from a Prismic Link field. It supports internal and external URLs and can render the appropriate component accordingly. If a link is internal to an app, the app may require a special
<Link>
component to be rendered rather than an<a>
element.<PrismicLink>
will automatically switch to that component as needed.If the link is configured with
target="_blank"
, which can be set within the Prismic editor,rel="noopener noreferrer"
will be added for improved security. Users can opt-out by providing their ownrel
prop.For
<RichText>
and Gatsby compatibility,PrismicLink
can take anhref
directly. Thehref
prop accepts a URL string that has already gone through the app's Link Resolver or an external URL. The URL can be internal or external with the same automatic adaption described previously.If
<PrismicProvider>
is used and configured with aninternalLinkComponent
prop, it will be used as<PrismicLink>
'sinternalComponent
prop. If aninternalComponent
prop is provided to<PrismicLink>
, it will take priority over the prop provided to<PrismicProvider>
. The same behavior is included forexternalComponent
.<PrismicToolbar>
Designed to be used with: React w/o a framework, Next.js, Gatsby
This component makes it convenient to add the Prismic Toolbar to an app. It automatically generates the correct script URL and includes the recommended configuration.
The output would be equivalent to the following HTML.
usePrismicDocument()
, et al.Designed to be used with: React w/o a framework
A collection of React hooks, including
usePrismicDocuments()
,usePrismicDocumentByID()
, andusePrismicDocumentsByType()
, queries the Prismic REST API for content from a Prismic repository.The API mirrors that of the
@prismicio/client
library in hook form. It handles different states automatically, such as loading states and data persistence between re-renders. API parameters, such aslang
andorderings
, are provided as each hook's last parameter, just like using the client directly.The following hooks fetch one or more documents. They return Prismic's paginated API responses which can be helpful when displaying paginated content.
usePrismicDocuments()
usePrismicDocumentsByIDs()
usePrismicDocumentsByTag()
usePrismicDocumentsByTags()
usePrismicDocumentsByType()
The following list of hooks is a variation of the previous list. These hooks automatically fetch all documents from a paginated response and may make multiple network requests in order to fetch all matching documents.
useAllPrismicDocuments()
useAllPrismicDocumentsByIDs()
useAllPrismicDocumentsByTag()
useAllPrismicDocumentsByTags()
useAllPrismicDocumentsByType()
The following hooks return one document.
usePrismicDocumentByID()
usePrismicDocumentByUID()
useFirstPrismicDocument()
: Returns only the first matching documentuseSinglePrismicDocument()
: Returns a singleton document of a given typeAll hooks return the following data shape:
data
: The paginated API response or document, depending on the hook. During a loading state, this prop isnull
.isLoading
:true
when the hook is fetching data,false
otherwise.error
: The error if the query fails.The
@prismicio/client
instance is provided using theclient
option in the hooks' last parameter.If
<PrismicProvider>
is used and configured with aclient
prop, it will be used as theclient
option. If aclient
option is provided to a hook, it will take priority over the prop provided to<PrismicProvider>
.How to provide feedback
This is a public request for comments on the proposed ideas. If you have any feedback or suggestions, please feel free to reply below.
We would like to hear from potential users of these ideas whether the ideas positively or negatively impact workflows. If you have any additional ideas as well, please share them here.
Everything posted here is open for feedback and is not final. Development may have already begun by the time you are reading this, but please do not let that stop you from providing feedback.
Thank you!
A note on an image component
An image component is intentionally not included in this RFC. If such a component were to be included, it would perform the following:
srcSet
generationUsers using React as part of a framework, like Next.js or Gatsby, already have access to deeply integrated image components with those features. They are widely used and well tested with a dedicated team behind them.
Users using React directly, including those using Create React App, can use Imgix's official React component,
react-imgix
, with the same benefits.As such, a
<PrismicImage>
component is not included in the RFC. We can, however, provide proper integrations with React frameworks throughgatsby-source-prismic
and@prismicio/next
(does not exist at the time of writing). We can also provide guidance for non-framework users.If you feel this is not the correct approach, please comment below.