Search code examples
reactjsviterollupjsvite-plugin-ssr

React is null on first render


I am using our custom library which is bundled with Vite but does exclude React, and I checked and I only have one instance of React and React is defined. But on the first render I am seeing the following error, when using vite-plugin-ssr (VPS). I also tried to redefine the hook inside the project, and then I don't see the error anymore.

react-dom.development.js:20662 Uncaught Error: Cannot read properties of null (reading 'useState')
    at updateDehydratedSuspenseComponent (react-dom.development.js:20662:17)
    at updateSuspenseComponent (react-dom.development.js:20362:16)
    at beginWork (react-dom.development.js:21624:14)
    at beginWork$1 (react-dom.development.js:27426:14)
    at performUnitOfWork (react-dom.development.js:26557:12)
    at workLoopSync (react-dom.development.js:26466:5)
    at renderRootSync (react-dom.development.js:26434:7)
    at performConcurrentWorkOnRoot (react-dom.development.js:25738:74)
    at workLoop (scheduler.development.js:266:34)
    at flushWork (scheduler.development.js:239:14)

I know that this can happen if:

  1. Missmatching React Versions => I checked both project component library and frontend project use 18.2.0
  2. react and react-dom not listed as peer dependency => also checked it is listed in component lib
  3. Singleton Hook Problem, I checked if I have more than one react instance. In Frontend project I attached something to React object and then console.log in component library and I can see the attached string
// in project
React.test = 'test'
...
// in component lib
console.log(React.test) // logs test

The configs of my component lib for bundleing: vite.config.ts

/// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import dts from 'vite-plugin-dts';

// https://vitejs.dev/config/
export default defineConfig({
  build: {
    lib: {
      entry: path.resolve(__dirname, 'src/index.ts'),
      formats: ['es', 'cjs'],
      name: '@workdigtital/component-library-react',
      fileName: (format) => `index.${format}.js`
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM'
        },
        exports: 'named'
      }
    }
  },
  plugins: [react(), dts({ insertTypesEntry: true })],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/tests/setup.ts'],
    exclude: ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**.stories**', '.storybook/**', '**.types**'],
    coverage: {
      all: true,
      exclude: ['**/*.stories.tsx', '.storybook/**'],
      provider: 'c8',
      reporter: ['cobertura', 'html']
    }
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@flaticon': path.resolve(__dirname, './node_modules/@flaticon')
    }
  }
});

I think I might bundle something wrong so it can be properly consumed by VPS? I don't see this issue in projects where we don't use VPS. Can anyone maybe point me into a direction what else I could investigate?

Appendix:

The hook in the component library:

function useWindowSize(): TWindowSize {
  console.log('useWindowSize');
  console.log('useWindowSize: React', React);
  console.log('useWindowSize: useState', useState);
  console.log('useWindowSize: useState', useEffect);
  const [windowSize, setWindowSize] = useState<TWindowSize>({
    width: 1600,
    height: 1200
  });
  useEffect(() => {
    function handleResize() {
      console.log('handleResize', React);
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }
    window.addEventListener('resize', handleResize);
    handleResize();
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
}

export default useWindowSize;

Vite Config of my project:

/// <reference types="vitest" />
import react from '@vitejs/plugin-react';
import autoprefixer from 'autoprefixer';
import { defineConfig } from 'vite';
import circleDependency from 'vite-plugin-circular-dependency';
import ssr from 'vite-plugin-ssr/plugin';
import path from 'path';

console.log(process.env.IS_STORYBOOK ? 'Vite uses plugin set for storybook' : 'Vite uses plugin set for app');

const plugins = process.env.IS_STORYBOOK
  ? [(react(), circleDependency({}))]
  : [
      (react(), circleDependency({})),
      ssr({
        prerender: true
      })
    ];

// https://vitejs.dev/config/
export default defineConfig({
  plugins,
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/tests/setup.ts'],
    exclude: ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**.stories**', '**.types**'],
    coverage: {
      all: true,
      provider: 'c8',
      reporter: ['cobertura', 'html']
    }
  },
  css: {
    postcss: {
      plugins: [autoprefixer()]
    }
  },
  resolve: {
    alias: {
      '#': path.resolve(__dirname, './src'),
      '#components': path.resolve(__dirname, './src/UI/components'),
      '#layouts': path.resolve(__dirname, './src/UI/layouts'),
      '#pages': path.resolve(__dirname, './src/pages'),
      '#providers': path.resolve(__dirname, './src/providers'),
      '#widgets': path.resolve(__dirname, './src/UI/widgets')
    }
  },
  ssr: {
    noExternal: ['primereact']
  },
  build: {
    sourcemap: 'inline'
  }
});

Tools I am using:

  • React
  • Vite-plugin-ssr

Solution

  • I reproduced your problem locally using your Vite configurations, and when i add the line import circleDependency from 'vite-plugin-circular-dependency' The issue appeard.

    To fix this issue, I would recommend removing circleDependency