Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for fullstack frameworks (SSR) #10575

Merged
merged 34 commits into from Jan 2, 2024
Merged

Support for fullstack frameworks (SSR) #10575

merged 34 commits into from Jan 2, 2024

Conversation

budnix
Copy link
Member

@budnix budnix commented Nov 10, 2023

Context

The PR, along with #10546, allows import and use of the Handsontable (as a client library) with an SSR/Fullstack framework like NextJS, Remix, Nuxt, and more.

Additionally, the PR bumps the wrapper dependency to React 18 (to utilize the useId hook). In this shape, it's still compatible with React 16, but in the near future, that support may be dropped.

The example using 'next/dynamic' module (NextJS)

// Grid.tsx
'use client'

import { registerAllModules } from 'handsontable/registry'
import { HotTable } from '@handsontable/react'

import 'handsontable/dist/handsontable.full.css'

registerAllModules();

type GridProps = {
  data: any;
};

export default function Grid(props: GridProps) {
  return (
    <div>
      <HotTable
        id="my-hot-table"
        data={props.data}
        licenseKey="non-commercial-and-evaluation"
      >
      </HotTable>
    </div>
  )
}

// page.tsx
import dynamic from 'next/dynamic';
import { data } from './constants';

const Grid = dynamic(() => import('./Grid'), { ssr: false });

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <Grid data={data}></Grid>
    </main>
  )
}

Demo https://stackblitz.com/edit/stackblitz-starters-rcgb6z

or using static imports (NextJS)

// Grid.tsx
'use client'

import { registerAllModules } from 'handsontable/registry'
import { HotTable } from '@handsontable/react'

import 'handsontable/dist/handsontable.full.css'

registerAllModules();

type GridProps = {
  data: any;
};

export default function Grid(props: GridProps) {
  return (
    <div>
      <HotTable
        id="my-hot-table"
        data={props.data}
        width={750}
        height={450}
        dropdownMenu={true}
        contextMenu={true}
        filters={true}
        rowHeaders={true}
        manualRowMove={true}
        navigableHeaders={true}
        licenseKey="non-commercial-and-evaluation"
      >
      </HotTable>
    </div>
  )
}

// page.tsx
import Grid from './Grid';
import { data } from './constants';

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <Grid data={data}></Grid>
    </main>
  )
}

Demo https://stackblitz.com/edit/stackblitz-starters-r9277n. The date cell type does not support the second approach as the editor depends on the client-side library Pikaday. For the SSR, the component is rendered twice, so it's advisable to set a unique id of the component that will be the same as on the server as on the client. Fixed by forking and fixing the Pikaday library handsontable/pikaday#1.

Remix (2.3) example
import type { MetaFunction } from "@remix-run/node";
import { ClientOnly } from "remix-utils/client-only";
import { data } from '../constants';
import Grid from "../Grid.client";

export const meta: MetaFunction = () => {
  return [
    { title: "New Remix App" },
    { name: "description", content: "Welcome to Remix!" },
  ];
};

export default function Index() {
  return (
    <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
      <h1>Welcome to Remix</h1>
      <ClientOnly fallback={null}>
        {() => <Grid data={data}></Grid>}
      </ClientOnly>
    </div>
  );
}
Gatsby (5.12) example
import { data } from "../components/constants";
import * as React from "react"
import type { HeadFC, PageProps } from "gatsby"
import FullGrid from "../components/FullGrid";

const IndexPage: React.FC<PageProps> = () => {
  return (
    <main>
      <FullGrid data={data}></FullGrid>
    </main>
  )
}

export default IndexPage

export const Head: HeadFC = () => <title>Home Page</title>
Nuxt (3.8) example
<template>
  <div>
    <h1>Welcome to the homepage</h1>
    <client-only>
      <full-grid></full-grid>
    </client-only>
  </div>
</template>

<script>
  import { defineComponent } from 'vue';
  import FullGrid from './full-hot.client.vue';

  export default defineComponent({
    data() {
      return {
        data: [
          ['', 'Ford', 'Volvo', 'Toyota', 'Honda'],
          ['2016', 10, 11, 12, 13],
          ['2017', 20, 11, 14, 13],
          ['2018', 30, 15, 12, 13]
        ],
      };
    },
    components: {
      FullGrid,
    }
  });
</script>

Live examples:

How has this been tested?

I tested the changes, and I was able to build and serve the app in the following cases:

Fullstack frameworks:

  • ✅ NextJS (using the dynamic function or static import with 'use client');
  • ✅ Remix (by wrapping the component within the ClientOnly of the remix-utils/client-only package);
  • ✅ Gatsby;
  • ✅ Nuxt;
  • ✅ SvelteKit;

Bundlers:

  • ✅ Webpack 4, 5;
  • ✅ Parcel
  • ✅ Vite

Types of changes

  • New feature or improvement (non-breaking change which adds functionality)

Related issue(s):

  1. fixes https://github.com/handsontable/dev-handsontable/issues/1541

Affected project(s):

  • handsontable
  • @handsontable/react

Checklist:

@budnix budnix self-assigned this Nov 10, 2023
Copy link

github-actions bot commented Nov 13, 2023

Launch the local version of documentation by running:

npm run docs:review 4bb91ab226c10def7425572cdd73ab998640915f

@krzysztofspilka
Copy link
Member

The change requires defining the id property of the React component (HotTable) the same on the server as on the client side.

This definitely requires a mention in the guides.

@budnix budnix marked this pull request as ready for review December 12, 2023 10:14
@jansiegel
Copy link
Member

jansiegel commented Dec 13, 2023

@evanSe
The main change in this PR is a change that throws a warning if the HotTable component does not have an id specified (intentions behind that change are listed in the PR description).
I have mixed feelings about that because:

  • The id being present is required just for the SSR implementations. As there's no way to recognize the SSR implementations on the client side, the warning is printed every time.
  • The warning doesn't specify the reasons for the id requirement. Maybe including information about SSR there would be a good idea?
  • Even if we do that, many of the current implementations of HotTable (I assume not all of those have id manually specified) would throw a warning on production.

There's an option of not including the warning at all and only specifying the requirement in the documentation, but as @budnix noticed, that would end up with a more cryptic React warning being thrown in the SSR cases, which is not a great look either.

Which approach do you suggest we go with?

@evanSe
Copy link
Member

evanSe commented Dec 13, 2023

@evanSe The main change in this PR is a change that throws a warning if the HotTable component does not have an id specified (intentions behind that change are listed in the PR description). I have mixed feelings about that because:

  • The id being present is required just for the SSR implementations. As there's no way to recognize the SSR implementations on the client side, the warning is printed every time.
  • The warning doesn't specify the reasons for the id requirement. Maybe including information about SSR there would be a good idea?
  • Even if we do that, many of the current implementations of HotTable (I assume not all of those have id manually specified) would throw a warning on production.

There's an option of not including the warning at all and only specifying the requirement in the documentation, but as @budnix noticed, that would end up with a more cryptic React warning being thrown in the SSR cases, which is not a great look either.

Which approach do you suggest we go with?

could we update our react wrapper perhaps to either have a default id always or perhaps https://react.dev/reference/react/useId which from my understanding generates the same id server and client

@budnix
Copy link
Member Author

budnix commented Dec 13, 2023

could we update our react wrapper perhaps to either have a default id always or perhaps https://react.dev/reference/react/useId which from my understanding generates the same id server and client

We can't use useId internally as the wrapper supports React 16 (the hook is available in 18). Default id would not work for multiple HotTable component presence.

@evanSe
Copy link
Member

evanSe commented Dec 13, 2023

could we update our react wrapper perhaps to either have a default id always or perhaps https://react.dev/reference/react/useId which from my understanding generates the same id server and client

We can't use useId internally as the wrapper supports React 16 (the hook is available in 18). Default id would not work for multiple HotTable component presence.

Perhaps we should upgrade to react 18 as 16 is very old now.

@AMBudnik
Copy link
Contributor

@evanSe we need to ask Chris about support for older versions of React. That's a business requirement.

We had this conversation here https://handsoncode.atlassian.net/wiki/spaces/SM/pages/5776260/Framework+Support in June, and the rule was 'support what is supported by the author' for all of the frameworks.

@evanSe
Copy link
Member

evanSe commented Dec 15, 2023

Before we merge this to develop we should do a QA run on it

@budnix
Copy link
Member Author

budnix commented Dec 18, 2023

@jansiegel @evanSe I bumped the React wrapper to use React v18. The code utilizes the useId hook when available, so there will be no warning in the console. Please re-review PR again.

@budnix budnix changed the title Support for SSR frameworks as a client-side library Support for fullstack frameworks Dec 18, 2023
@krzysztofspilka krzysztofspilka changed the title Support for fullstack frameworks Support for fullstack frameworks (SSR) Dec 18, 2023
Copy link
Member

@jansiegel jansiegel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@budnix budnix merged commit 3388fcb into develop Jan 2, 2024
23 checks passed
@budnix budnix deleted the feature/dev-issue-1541 branch January 2, 2024 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants