splitbee/react-notion

Proposal: NotionComponentsProvider

transitive-bullshit opened this issue · 2 comments

The idea is to make react-notion more extensible by allowing users to optionally override individual components.

This is extremely useful in practice. For example, using next/link within a Next.js app if you want to override all of the page link components (where Next.js does some fancy pre-fetching that's disabled if you just use normal <a> tags). Or if you want to override all img instances to use lazy loading. Or if you want to use a custom table component in the future. etc. etc.

Docz has a great implementation of this by @pedronauck. Here's links to their implementation and docs.

The idea is to add a single hook useNotionComponents that uses React Context to get a component mapping that users can override via a NotionComponentsProvider.

This would also solve #10 and deprecate #11 @josemussa because you could override the link component and provide whatever default props you want.

I've already implemented this for notion2site.

Here's a quick overview of my implementation which is a slimmed down version of Docz's impl:

import * as React from 'react'
import { SFC, useContext, createContext } from 'react'

export interface NotionComponentsMap {
  [key: string]: any
}

const DefaultLink: SFC = (props) => <a {...props} />

const defaultComponents: NotionComponentsMap = {
  link: DefaultLink,
  pageLink: DefaultLink
}

export interface ComponentsProviderProps {
  components: NotionComponentsMap
}

const ctx = createContext<NotionComponentsMap>(defaultComponents)
export const NotionComponentsProvider: SFC<ComponentsProviderProps> = ({
  components: themeComponents = {},
  children
}) => (
  <ctx.Provider value={{ ...defaultComponents, ...themeComponents }}>
    {children}
  </ctx.Provider>
)

export const useNotionComponents = (): NotionComponentsMap => {
  return useContext(ctx)
}

And then in block.tsx:

export const Block: React.FC<Block> = (props) => {
  // ...
  const components = useNotionComponents()
  // ...
  
  return (
    <components.pageLink
      className='notion-page-link'
      href={mapPageUrl(blockValue.id)}
    >
      ...
    </components.pageLink
  )
}

And here's how a user of react-notion would configure this (optional):

import Link from 'next/link'

// ...
render() {
  return (
      <NotionComponentsProvider
        components={{
          pageLink: ({ href = '', ...rest }) => {
            return (
              <Link href='/[pageId'] as={href}>
                <a {...rest} />
              </Link>
            )
          }
        }}
      >
        <NotionRenderer
          blockMap={blockMap}
          mapPageUrl={mapPageUrl}
          fullPage
        />
      </NotionComponentsProvider>
  )
}

sounds too good to be true! We are just starting to use react-notion and are dreaming of using next-optimized-images (in the new version 3 ) and for sure next/link next to custom buttons to match the common style of our page.
Furthermore, the global stylesheet right now seems to be impossible to scope as component-level css https://nextjs.org/docs/basic-features/built-in-css-support#adding-component-level-css :(

fixed by #30