Docusaurus: [v2] add a way to exclude components from SSR within JSX

Created on 6 Feb 2020  路  14Comments  路  Source: facebook/docusaurus

馃殌 Feature

Add a way to exclude components from SSR in JSX. Maybe somethin like <NoSSR></NoSSR>.

Have you read the Contributing Guidelines on issues?

yes

Motivation

Right now, each time we use a react lib that doesn't support SSR we have to put this check {typeof window !== 'undefined' && .... } everywhere inside js and mdx files.

Pitch

Tools like Next js and nuxt provide a way to exclude components from the SSR within the markup. To avoid errors during the build phase we should be able to do se same in a better way than using {typeof window !== 'undefined' && .... } or {typeof document !== 'undefined' && .... }.

feature

Most helpful comment

Fine, I wrote it on my todo list :notebook_with_decorative_cover:

All 14 comments

I created https://github.com/facebook/docusaurus/pull/2296 which can be used as a convenient API instead of a component. Do you have examples of the API in Next and Nuxt which I can take a look at?

I imagine the component code would just be the following

function ClientOnly({children}) {
  if (typeof window === 'undefined') {
    return null;
  }

  return children;
}

Since the logic is so simple, I'm not sure whether it is worth including it in Docusaurus core. A convenient imperative API might be good enough and you can create your own declarative component wrapper 馃

New Nuxt version has this component to check the environment from the template:
https://nuxtjs.org/api/components-client-only/

<client-only placeholder="Loading...">
      <!-- this component will only be rendered on client-side -->
      <comments />
</client-only>

v2.9.0 < there was a <no-ssr> component wrapper.


Nextjs has a {ssr: false} option to use with dynamic imports:
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr

import dynamic from 'next/dynamic'

const DynamicComponentWithNoSSR = dynamic(
  () => import('../components/hello3'),
  { ssr: false }
)

@yangshun I would add a similar component just because then we can insert code directly into JSX instead of using useEffect, for example, for our Feedback page, we could do this:

function Feedback() {
  return (
    <Layout
      permalink="/feedback"
      title="Feedback"
      description="Docusaurus 2 Feedback page">
      <div
        className={classnames(
          'container',
          'margin-vert--xl',
          styles.feedbackBackground,
        )}
        data-canny
      />

      <NoSSR>
        window.Canny('render', {
            boardToken: BOARD_TOKEN,
            basePath: '/feedback',
        });
      </NoSSR>
    </Layout>
  );
}

Ok I think we can add that, but it is better named <ClientOnly>/<ClientRenderedOnly> rather than <NoSSR>.

Fine, I wrote it on my todo list :notebook_with_decorative_cover:

@equinusocio can you please give a specific example (or use case) when a similar component could be useful? Maybe the new API will help with this, could you look? https://github.com/facebook/docusaurus/pull/2296

@lex111 I don't have a public example available, but recently at @OvalMoney we used this react data table component which don't works inside docusaurus v2 mdx files because it's client-only and when running the build process, docusaurus throw an error about missing window object during the server building.

We had to wrap it inside a custom <NoSRR> component to make the build working. I think this may help in every similar situations where a client-only react lib is used.

@equinusocio but could you give me a piece of code? I ask this more for documentation purposes, it would be nice to give a real-world example. This tiny component, so no problem with adding it to the core, but I want to get more information on its possible using, as previously I had a wrong idea about in which cases it can be used.

I don't know if this is what you need, but this our case:

data-table.mdx

Once you import it, you can use the `<DataTable>` element inside your JSX template.

<NoSSR>
    <DataTable
      columns={[{
        name: 'First column',
        selector: 'someKey',
        sortable: true,
      }, {
        name: 'Second column',
        selector: 'anotherKey',
        sortable: true,
      }]}
      data={[{
        id: 1,
        someKey: 'Some data to print',
        anotherKey: 'Another key data',
        anyOtherKey: 'Any other key data'
      }, {
        id: 2,
        someKey: 'Some data to print 2',
        anotherKey: 'Another key data 2',
        anyOtherKey: 'Any other key data 2'
      }]}
    />
</NoSSR>

We had to exclude the component from the build process because it search for the window object.

Sorry i forgot to mention that the table component is this one
https://www.npmjs.com/package/react-data-table-component

We had to exclude the component from the build process because it search for the window object.

@equinusocio oddly, I tried using the example above, and did not get any build errors. Are you sure that the error was in this component?

import DataTable from 'react-data-table-component';

<DataTable
  columns={[{
    name: 'First column',
    selector: 'someKey',
    sortable: true,
  }, {
    name: 'Second column',
    selector: 'anotherKey',
    sortable: true,
  }]}
  data={[{
    id: 1,
    someKey: 'Some data to print',
    anotherKey: 'Another key data',
    anyOtherKey: 'Any other key data'
  }, {
    id: 2,
    someKey: 'Some data to print 2',
    anotherKey: 'Another key data 2',
    anyOtherKey: 'Any other key data 2'
  }]}
/>

The componet listen for window resize when the hide prop is used:
https://www.npmjs.com/package/react-data-table-component#columns

@equinusocio thanks, I added hide prop but still build is going to build successfully, what other options are there how to make build fail?

import DataTable from 'react-data-table-component';

<DataTable
  columns={[{
    name: 'First column',
    selector: 'someKey',
    sortable: true,
    hide: 'md',
  }, {
    name: 'Second column',
    selector: 'anotherKey',
    sortable: true,
    hide: 'md',
  }]}
  data={[{
    id: 1,
    someKey: 'Some data to print',
    anotherKey: 'Another key data',
    anyOtherKey: 'Any other key data'
  }, {
    id: 2,
    someKey: 'Some data to print 2',
    anotherKey: 'Another key data 2',
    anyOtherKey: 'Any other key data 2'
  }]}
/>

@lex111 Sorry for the late response, i've checked again and i can confirm that one of the data table props cause the issue. Here the error we get (docusaurus alpha40) during the build process, by removing our custom <NoSSR> that wraps the table inside the mdx file:

ReferenceError: document is not defined
(undefined) ReferenceError: document is not defined
    at index_es_Ee (main:2775:30551)
    at Ht (main:2775:60546)
    at d (main:29456:498)
    at Za (main:29459:16)
    at a.b.render (main:29464:476)
    at a.b.read (main:29464:18)
    at Object.renderToString (main:29474:364)
    at render (main:69344:252)

Here are the table props we're using:

data
progressComponent
onRowClicked
subHeader
progressPending
expandableRowsComponent
pagination
highlightOnHover
pointerOnHover
responsive
fixedHeader
noHeader
paginationPerPage
striped
noDataComponent
theme
customStyles

A side note, we'are also using the component createTheme function to customize the table, and this may be the cause. Here the code of the theme, and we assign it to the table theme property like so theme: 'ODS'

import { createTheme } from 'react-data-table-component';

createTheme('ODS', {
  text: {
    primary: `hsl(${Tokens['secondary-80']})`,
    secondary: `hsl(${Tokens['primary-100']})`,
  },
  background: {
    default: 'transparent',
  },
  context: {
    background: 'transparent',
  },
  button: {
    default: `hsl(${Tokens['secondary-80']})`,
    hover: `hsl(${Tokens['secondary-40']})`,
    disabled: `hsl(${Tokens['secondary-70']})`,
  },
  selected: {
    default: `hsl(${Tokens['secondary-50']})`,
    text: `hsl(${Tokens['secondary-90']})`,
  },
  sortFocus: {
    default: `hsl(${Tokens['secondary-80']})`,
  },
  divider: {
    default: 'transparent',
  },
  highlightOnHover: {
    default: `hsl(${Tokens['secondary-50']})`,
    text: `hsl(${Tokens['secondary-80']})`,
  },
  striped: {
    default: `hsl(${Tokens['secondary-40']})`,
    text: `hsl(${Tokens['secondary-80']})`,
  },
});
Was this page helpful?
0 / 5 - 0 ratings

Related issues

philipmjohnson picture philipmjohnson  路  3Comments

muuvmuuv picture muuvmuuv  路  3Comments

azu picture azu  路  3Comments

omry picture omry  路  3Comments

itelofilho picture itelofilho  路  3Comments