vite icon indicating copy to clipboard operation
vite copied to clipboard

HMR breaks when modifying React context provider

Open selrond opened this issue 3 years ago • 50 comments

Describe the bug

Vite HMR breaks when modifying React context provider Related: https://github.com/vitejs/vite-plugin-react/issues/24

Reproduction

selrond/vite-react-usecontext

System Info

Output of npx envinfo --system --npmPackages vite,@vitejs/plugin-vue --binaries --browsers:

  System:
    OS: macOS 10.15.7
    CPU: (8) x64 Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
    Memory: 413.26 MB / 16.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 15.14.0 - ~/.nvm/versions/node/v15.14.0/bin/node
    Yarn: 1.22.10 - /usr/local/bin/yarn
    npm: 7.7.6 - ~/.nvm/versions/node/v15.14.0/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Browsers:
    Brave Browser: 89.1.21.76
    Chrome: 90.0.4430.93
    Firefox: 87.0
    Firefox Developer Edition: 89.0
    Safari: 14.0.3
  npmPackages:
    vite: ^2.2.3 => 2.2.4 

Used package manager: npm

Logs


Before submitting the issue, please make sure you do the following

  • [x] Read the Contributing Guidelines.
  • [x] Read the docs.
  • [x] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • [x] Provide a description in this issue that describes the bug.
  • [x] Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to https://github.com/vuejs/vue-next instead.
  • [x] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.

selrond avatar May 07 '21 19:05 selrond

In my case the context mechanism behind react seems to break - I get 2 renders of the same component:

  • 1 with the default react context value (even though provider is specified)
  • 1 with the value from the provider

My config for vite is empty so no other plugins can be influencing this. The only thing that helps is restarting vite (simple reload of the website does not work so it must be caching of HMR even on reloads). Disabling HMR entirely server.hmr = false fixes the issue at the cost of no HMR.

After changing more things afterwards I suddenly started getting a weird _s is not defined in the component even with the most barebones context when HMR kicks in after changing anything. (again fixed by restarting vite).

App.tsx:25 Uncaught TypeError: _s is not a function
    at App (App.tsx:25)
    at renderWithHooks (react-dom.development.js:14985)
    at mountIndeterminateComponent (react-dom.development.js:17811)
    at beginWork (react-dom.development.js:19049)
    at HTMLUnknownElement.callCallback2 (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at beginWork$1 (react-dom.development.js:23964)
    at performUnitOfWork (react-dom.development.js:22776)
    at workLoopSync (react-dom.development.js:22707)
Debug trace from Vite
$ vite --debug
  vite:config TS + native esm config loaded in 27ms URL {
  href: 'file:///home/some-random-app/packages/app/vite.config.ts',
  origin: 'null',
  protocol: 'file:',
  username: '',
  password: '',
  host: '',
  hostname: '',
  port: '',
  pathname: '/home/some-random-app/packages/app/vite.config.ts',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
} +0ms
  vite:config using resolved config: {
  vite:config   server: { fs: { strict: undefined, allow: [Array] } },
  vite:config   configFile: '/home/some-random-app/packages/app/vite.config.ts',
  vite:config   configFileDependencies: [ 'vite.config.ts' ],
  vite:config   inlineConfig: {
  vite:config     root: undefined,
  vite:config     base: undefined,
  vite:config     mode: undefined,
  vite:config     configFile: undefined,
  vite:config     logLevel: undefined,
  vite:config     clearScreen: undefined,
  vite:config     server: { fs: [Object] }
  vite:config   },
  vite:config   root: '/home/some-random-app/packages/app',
  vite:config   base: '/',
  vite:config   resolve: { dedupe: undefined, alias: [ [Object], [Object] ] },
  vite:config   publicDir: '/home/some-random-app/packages/app/public',
  vite:config   cacheDir: '/home/some-random-app/packages/app/node_modules/.vite',
  vite:config   command: 'serve',
  vite:config   mode: 'development',
  vite:config   isProduction: false,
  vite:config   plugins: [
  vite:config     'vite:pre-alias',
  vite:config     'alias',
  vite:config     'vite:modulepreload-polyfill',
  vite:config     'vite:resolve',
  vite:config     'vite:html',
  vite:config     'vite:css',
  vite:config     'vite:esbuild',
  vite:config     'vite:json',
  vite:config     'vite:wasm',
  vite:config     'vite:worker',
  vite:config     'vite:asset',
  vite:config     'vite:define',
  vite:config     'vite:css-post',
  vite:config     'vite:client-inject',
  vite:config     'vite:import-analysis'
  vite:config   ],
  vite:config   build: {
  vite:config     target: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
  vite:config     polyfillModulePreload: true,
  vite:config     outDir: 'dist',
  vite:config     assetsDir: 'assets',
  vite:config     assetsInlineLimit: 4096,
  vite:config     cssCodeSplit: true,
  vite:config     sourcemap: false,
  vite:config     rollupOptions: {},
  vite:config     commonjsOptions: { include: [Array], extensions: [Array] },
  vite:config     dynamicImportVarsOptions: { warnOnError: true, exclude: [Array] },
  vite:config     minify: 'terser',
  vite:config     terserOptions: {},
  vite:config     write: true,
  vite:config     emptyOutDir: null,
  vite:config     manifest: false,
  vite:config     lib: false,
  vite:config     ssr: false,
  vite:config     ssrManifest: false,
  vite:config     brotliSize: true,
  vite:config     chunkSizeWarningLimit: 500,
  vite:config     watch: null
  vite:config   },
  vite:config   env: {
  vite:config     VITE_FUZZ_LOCALES: 'true',
  vite:config     BASE_URL: '/',
  vite:config     MODE: 'development',
  vite:config     DEV: true,
  vite:config     PROD: false
  vite:config   },
  vite:config   assetsInclude: [Function: assetsInclude],
  vite:config   logger: {
  vite:config     hasWarned: false,
  vite:config     info: [Function: info],
  vite:config     warn: [Function: warn],
  vite:config     warnOnce: [Function: warnOnce],
  vite:config     error: [Function: error],
  vite:config     clearScreen: [Function: clearScreen],
  vite:config     hasErrorLogged: [Function: hasErrorLogged]
  vite:config   },
  vite:config   createResolver: [Function: createResolver],
  vite:config   optimizeDeps: { esbuildOptions: { keepNames: undefined } }
  vite:config } +5ms
  vite:deps Hash is consistent. Skipping. Use --force to override. +0ms

vite v2.5.1 dev server running at:

Local: http://localhost:3000/ Network: use --host to expose

ready in 166ms.

vite:time 1ms / +0ms vite:spa-fallback Rewriting GET / to /index.html +0ms vite:time 17ms /index.html +55ms vite:resolve 1ms /@vite/client -> /home/some-random-app/node_modules/vite/dist/client/client.mjs +0ms vite:load 1ms [fs] /@vite/client +0ms vite:resolve 0ms @vite/env -> /home/some-random-app/node_modules/vite/dist/client/env.mjs +9ms vite:transform 6ms /@vite/client +0ms vite:time 13ms /@vite/client +123ms vite:resolve 1ms /src/main.tsx -> /home/some-random-app/packages/app/src/main.tsx +3ms vite:load 1ms [fs] /src/main.tsx +11ms vite:resolve 0ms react -> /home/some-random-app/packages/app/node_modules/.vite/react.js?v=c73c3b06&es-interop +6ms vite:resolve 0ms /node_modules/.vite/react.js?v=c73c3b06&es-interop -> /home/some-random-app/packages/app/node_modules/.vite/react.js?v=c73c3b06&es-interop +0ms vite:resolve 0ms react-dom -> /home/some-random-app/packages/app/node_modules/.vite/react-dom.js?v=c73c3b06&es-interop +3ms vite:resolve 0ms /node_modules/.vite/react-dom.js?v=c73c3b06&es-interop -> /home/some-random-app/packages/app/node_modules/.vite/react-dom.js?v=c73c3b06&es-interop +1ms vite:resolve 2ms app/src/components/organisms -> /home/some-random-app/packages/app/src/components/organisms/index.ts +3ms vite:resolve 0ms /src/components/organisms/index.ts -> /home/some-random-app/packages/app/src/components/organisms/index.ts +0ms vite:resolve 1ms app/src/services/api-client/api-client -> /home/some-random-app/packages/app/src/services/api-client/api-client.ts +1ms vite:resolve 0ms /src/services/api-client/api-client.ts -> /home/some-random-app/packages/app/src/services/api-client/api-client.ts +0ms vite:resolve 0ms app/src/services/users/users -> /home/some-random-app/packages/app/src/services/users/users.ts +0ms vite:resolve 0ms /src/services/users/users.ts -> /home/some-random-app/packages/app/src/services/users/users.ts +0ms vite:resolve 1ms app/src/services/localization/localization -> /home/some-random-app/packages/app/src/services/localization/localization.ts +1ms vite:resolve 0ms /src/services/localization/localization.ts -> /home/some-random-app/packages/app/src/services/localization/localization.ts +0ms vite:resolve 0ms app/src/contexts -> /home/some-random-app/packages/app/src/contexts/index.ts +0ms vite:resolve 0ms /src/contexts/index.ts -> /home/some-random-app/packages/app/src/contexts/index.ts +0ms vite:resolve 0ms /node_modules/.vite/react.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/react.js?v=c73c3b06 +0ms vite:resolve 0ms /node_modules/.vite/react-dom.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/react-dom.js?v=c73c3b06 +1ms vite:transform 15ms /src/main.tsx +18ms vite:time 18ms /src/main.tsx +18ms vite:load 0ms [fs] npm: vite +56ms vite:rewrite 0ms [no imports] /home/some-random-app/node_modules/vite/dist/client/env.mjs +0ms vite:transform 0ms npm: vite +42ms vite:time 2ms npm: vite +41ms vite:load 0ms [fs] /src/components/organisms/index.ts +9ms vite:resolve 0ms ./app/App -> /home/some-random-app/packages/app/src/components/organisms/app/App.tsx +53ms vite:resolve 0ms /src/components/organisms/app/App.tsx -> /home/some-random-app/packages/app/src/components/organisms/app/App.tsx +1ms vite:transform 3ms /src/components/organisms/index.ts +12ms vite:time 5ms /src/components/organisms/index.ts +12ms vite:load 1ms [fs] /src/services/api-client/api-client.ts +6ms vite:resolve 1ms app/src/batteries/server-client/server-client -> /home/some-random-app/packages/app/src/batteries/server-client/server-client.ts +6ms vite:resolve 0ms /src/batteries/server-client/server-client.ts -> /home/some-random-app/packages/app/src/batteries/server-client/server-client.ts +0ms vite:resolve 0ms app/src/batteries/singleton/singleton -> /home/some-random-app/packages/app/src/batteries/singleton/singleton.ts +0ms vite:resolve 1ms /src/batteries/singleton/singleton.ts -> /home/some-random-app/packages/app/src/batteries/singleton/singleton.ts +1ms vite:resolve 0ms app/src/config -> /home/some-random-app/packages/app/src/config.ts +0ms vite:resolve 0ms /src/config.ts -> /home/some-random-app/packages/app/src/config.ts +0ms vite:transform 5ms /src/services/api-client/api-client.ts +7ms vite:time 6ms /src/services/api-client/api-client.ts +7ms vite:load 0ms [fs] /src/services/users/users.ts +7ms vite:resolve 0ms server/src/api/user/route -> /home/some-random-app/packages/server/src/api/user/route.ts +6ms vite:resolve 0ms app/src/batteries/state/state -> /home/some-random-app/packages/app/src/batteries/state/state.ts +1ms vite:resolve 0ms /src/batteries/state/state.ts -> /home/some-random-app/packages/app/src/batteries/state/state.ts +0ms vite:transform 6ms /src/services/users/users.ts +8ms vite:time 7ms /src/services/users/users.ts +8ms vite:load 0ms [fs] /src/services/localization/localization.ts +8ms vite:resolve 1ms i18next -> /home/some-random-app/packages/app/node_modules/.vite/i18next.js?v=c73c3b06 +6ms vite:resolve 0ms /node_modules/.vite/i18next.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/i18next.js?v=c73c3b06 +0ms vite:resolve 0ms react-i18next -> /home/some-random-app/packages/app/node_modules/.vite/react-i18next.js?v=c73c3b06 +0ms vite:resolve 0ms /node_modules/.vite/react-i18next.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/react-i18next.js?v=c73c3b06 +0ms vite:resolve 0ms i18next-icu -> /home/some-random-app/packages/app/node_modules/.vite/i18next-icu.js?v=c73c3b06 +0ms vite:resolve 0ms /node_modules/.vite/i18next-icu.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/i18next-icu.js?v=c73c3b06 +0ms vite:resolve 0ms i18next-http-backend -> /home/some-random-app/packages/app/node_modules/.vite/i18next-http-backend.js?v=c73c3b06 +0ms vite:resolve 0ms /node_modules/.vite/i18next-http-backend.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/i18next-http-backend.js?v=c73c3b06 +1ms vite:resolve 0ms ./fuzzer -> /home/some-random-app/packages/app/src/services/localization/fuzzer.ts +1ms vite:resolve 0ms /src/services/localization/fuzzer.ts -> /home/some-random-app/packages/app/src/services/localization/fuzzer.ts +0ms vite:transform 6ms /src/services/localization/localization.ts +8ms vite:time 7ms /src/services/localization/localization.ts +8ms vite:load 0ms [fs] /src/contexts/index.ts +9ms vite:resolve 0ms ./services/services -> /home/some-random-app/packages/app/src/contexts/services/services.ts +7ms vite:resolve 0ms /src/contexts/services/services.ts -> /home/some-random-app/packages/app/src/contexts/services/services.ts +0ms vite:transform 3ms /src/contexts/index.ts +6ms vite:time 5ms /src/contexts/index.ts +7ms vite:load 0ms [fs] /src/components/organisms/app/App.tsx +9ms vite:resolve 0ms app/src/batteries/style/style -> /home/some-random-app/packages/app/src/batteries/style/style.ts +10ms vite:resolve 1ms /src/batteries/style/style.ts -> /home/some-random-app/packages/app/src/batteries/style/style.ts +1ms vite:resolve 2ms app/src/main -> /home/some-random-app/packages/app/src/main.tsx +2ms vite:resolve 1ms daisyui/dist/full.css -> /home/some-random-app/node_modules/daisyui/dist/full.css +1ms vite:transform 7ms /src/components/organisms/app/App.tsx +14ms vite:time 9ms /src/components/organisms/app/App.tsx +13ms vite:load 1ms [fs] /src/batteries/server-client/server-client.ts +21ms vite:resolve 0ms @trpc/client -> /home/some-random-app/packages/app/node_modules/.vite/@trpc_client.js?v=c73c3b06 +17ms vite:resolve 0ms /node_modules/.vite/@trpc_client.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/@trpc_client.js?v=c73c3b06 +0ms vite:transform 4ms /src/batteries/server-client/server-client.ts +17ms vite:time 6ms /src/batteries/server-client/server-client.ts +18ms vite:load 2ms [fs] /src/config.ts +8ms vite:load 1ms [fs] /src/batteries/singleton/singleton.ts +0ms vite:transform 1ms /src/config.ts +5ms vite:time 3ms /src/config.ts +4ms vite:rewrite 0ms [no imports] src/batteries/singleton/singleton.ts +81ms vite:transform 5ms /src/batteries/singleton/singleton.ts +4ms vite:time 7ms /src/batteries/singleton/singleton.ts +4ms vite:load 1ms [fs] ../server/src/api/user/route.ts +16ms vite:load 1ms [fs] /src/batteries/state/state.ts +0ms vite:resolve 1ms server/src/routes -> /home/some-random-app/packages/server/src/routes.ts +24ms vite:transform 4ms ../server/src/api/user/route.ts +15ms vite:time 6ms ../server/src/api/user/route.ts +15ms vite:resolve 0ms meiosis-setup/immer -> /home/some-random-app/packages/app/node_modules/.vite/meiosis-setup_immer.js?v=c73c3b06 +1ms vite:resolve 0ms /node_modules/.vite/meiosis-setup_immer.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/meiosis-setup_immer.js?v=c73c3b06 +0ms vite:resolve 0ms meiosis-setup/simple-stream -> /home/some-random-app/packages/app/node_modules/.vite/meiosis-setup_simple-stream.js?v=c73c3b06 +0ms vite:resolve 0ms /node_modules/.vite/meiosis-setup_simple-stream.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/meiosis-setup_simple-stream.js?v=c73c3b06 +0ms vite:resolve 0ms immer -> /home/some-random-app/packages/app/node_modules/.vite/immer.js?v=c73c3b06 +0ms vite:resolve 0ms /node_modules/.vite/immer.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/immer.js?v=c73c3b06 +1ms vite:transform 5ms /src/batteries/state/state.ts +2ms vite:time 7ms /src/batteries/state/state.ts +2ms vite:load 0ms [fs] /src/contexts/services/services.ts +32ms vite:load 0ms [fs] /src/batteries/style/style.ts +2ms vite:resolve 0ms nanoid/non-secure -> /home/some-random-app/packages/app/node_modules/.vite/nanoid_non-secure.js?v=c73c3b06 +31ms vite:resolve 0ms /node_modules/.vite/nanoid_non-secure.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/nanoid_non-secure.js?v=c73c3b06 +0ms vite:transform 5ms /src/contexts/services/services.ts +31ms vite:time 6ms /src/contexts/services/services.ts +31ms vite:resolve 0ms twind/css -> /home/some-random-app/packages/app/node_modules/.vite/twind_css.js?v=c73c3b06 +2ms vite:resolve 1ms /node_modules/.vite/twind_css.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/twind_css.js?v=c73c3b06 +1ms vite:transform 5ms /src/batteries/style/style.ts +3ms vite:time 7ms /src/batteries/style/style.ts +3ms vite:load 3ms [fs] npm: daisyui +8ms vite:load 1ms [fs] ../server/src/routes.ts +8ms vite:hmr [self-accepts] /home/some-random-app/node_modules/daisyui/dist/full.css +0ms vite:transform 407ms npm: daisyui +412ms vite:time 416ms npm: daisyui +414ms vite:rewrite 1ms [no imports] /home/some-random-app/packages/server/src/routes.ts +466ms vite:transform 405ms ../server/src/routes.ts +3ms vite:time 408ms ../server/src/routes.ts +2ms vite:resolve 0ms /node_modules/.vite/chunk-IHTDASF6.js -> /home/some-random-app/packages/app/node_modules/.vite/chunk-IHTDASF6.js +497ms vite:time 3ms /node_modules/.vite/chunk-IHTDASF6.js.map +84ms vite:load 1ms [fs] /src/services/localization/fuzzer.ts +541ms vite:resolve 0ms pseudo-localization -> /home/some-random-app/packages/app/node_modules/.vite/pseudo-localization.js?v=c73c3b06&es-interop +56ms vite:resolve 0ms /node_modules/.vite/pseudo-localization.js?v=c73c3b06&es-interop -> /home/some-random-app/packages/app/node_modules/.vite/pseudo-localization.js?v=c73c3b06&es-interop +0ms vite:resolve 0ms /node_modules/.vite/pseudo-localization.js?v=c73c3b06 -> /home/some-random-app/packages/app/node_modules/.vite/pseudo-localization.js?v=c73c3b06 +1ms vite:transform 4ms /src/services/localization/fuzzer.ts +140ms vite:time 5ms /src/services/localization/fuzzer.ts +55ms vite:time 1ms /src/locales/en/common.json +165ms vite:hmr [file change] src/main.tsx +22s

THIS IS WHERE I ADD A SINGLE COMMA TO main.tsx AND SAVE - NOW CONTEXT IS FIRING TWICE WITH THE ABOVE PROBLEM

8:52:02 PM [vite] page reload src/main.tsx vite:spa-fallback Rewriting GET / to /index.html +22s vite:time 6ms /index.html +21s vite:cache [304] /@vite/client +0ms vite:time 1ms /@vite/client +46ms vite:load 0ms [fs] /src/main.tsx +22s vite:transform 6ms /src/main.tsx +22s vite:time 7ms /src/main.tsx +8ms vite:cache [304] npm: vite +79ms vite:time 0ms npm: vite +71ms vite:cache [304] /src/services/api-client/api-client.ts +9ms vite:time 0ms /src/services/api-client/api-client.ts +9ms vite:cache [304] /src/services/users/users.ts +1ms vite:time 0ms /src/services/users/users.ts +1ms vite:load 1ms [fs] /src/components/organisms/index.ts +88ms vite:transform 2ms /src/components/organisms/index.ts +84ms vite:time 5ms /src/components/organisms/index.ts +3ms vite:cache [304] /src/services/localization/localization.ts +5ms vite:time 1ms /src/services/localization/localization.ts +3ms vite:cache [304] /src/contexts/index.ts +2ms vite:time 1ms /src/contexts/index.ts +1ms vite:cache [304] /src/batteries/server-client/server-client.ts +14ms vite:time 1ms /src/batteries/server-client/server-client.ts +14ms vite:cache [304] /src/batteries/singleton/singleton.ts +16ms vite:time 1ms /src/batteries/singleton/singleton.ts +16ms vite:cache [304] /src/config.ts +0ms vite:time 1ms /src/config.ts +1ms vite:cache [304] ../server/src/api/user/route.ts +3ms vite:time 1ms ../server/src/api/user/route.ts +2ms vite:cache [304] /src/batteries/state/state.ts +17ms vite:time 1ms /src/batteries/state/state.ts +17ms vite:load 1ms [fs] /src/components/organisms/app/App.tsx +58ms vite:transform 3ms /src/components/organisms/app/App.tsx +58ms vite:time 5ms /src/components/organisms/app/App.tsx +5ms vite:cache [304] /src/contexts/services/services.ts +28ms vite:time 1ms /src/contexts/services/services.ts +24ms vite:cache [304] ../server/src/routes.ts +6ms vite:time 0ms ../server/src/routes.ts +5ms vite:cache [304] /src/batteries/style/style.ts +12ms vite:time 0ms /src/batteries/style/style.ts +12ms vite:cache [memory] /src/main.tsx +0ms vite:time 0ms /src/main.tsx +1ms vite:cache [304] npm: daisyui +6ms vite:time 1ms npm: daisyui +5ms vite:time 2ms /node_modules/.vite/chunk-IHTDASF6.js.map +126ms vite:cache [304] /src/services/localization/fuzzer.ts +213ms vite:time 0ms /src/services/localization/fuzzer.ts +87ms vite:time 0ms /src/locales/en/common.json +62ms

I've added a comment about halfway through the trace where I save a file and Vite recompiles causing the problems described above.

Stacktraces of the 2 context calls done in the component console.log(useContext(...))
App | @ | App.tsx:28
-- | -- | --
  | renderWithHooks | @ | react-dom.development.js:14985
  | mountIndeterminateComponent | @ | react-dom.development.js:17811
  | beginWork | @ | react-dom.development.js:19049
  | beginWork$1 | @ | react-dom.development.js:23940
  | performUnitOfWork | @ | react-dom.development.js:22779
  | workLoopSync | @ | react-dom.development.js:22707
  | renderRootSync | @ | react-dom.development.js:22670
  | performSyncWorkOnRoot | @ | react-dom.development.js:22293
  | scheduleUpdateOnFiber | @ | react-dom.development.js:21881                               

There's nothing in the component to make it render twice (and when Vite restarts before HMR is done it does render only once). Yet here 2 calls are made that differ in how they were triggered

I've traced down the problem to defining the context in the same file where I called ReactDOM.render. Since I defined the context here I also export it (to be able to consume it in other components). Since this is the place I call the final render though it's also the place I import those components which creates a cyclic dependency.

The problem then is either the cyclicity and HMR breaking it. Or HMR doing something to the compiled code which creates the context twice which then causes a whole bunch of errors and weird behaviour.

Either way, don't define context's in the same file as you call ReactDOM.render kids #TIL

IgnusG avatar Aug 29 '21 18:08 IgnusG

I am also facing the same issue and indeed it's related to Vite or @vitejs/plugin-react. I have tried to run the same application using the react-scripts package (used by create-react-app) and I was able to preserve the state even after modifying the context provider.

Wondering if it's because Vite HMR triggers component reload (resetting any previous state) on modifying initial state values:


// CounterContext.jsx
const CounterContext = createContext({
  counter: 0,
  setCounter: (value) => { console.log('this is default value') }
})
/* CouterProvider ... */


// MyComponent.jsx
const MyComponent = (props) => {
  const { counter, setCounter } = useContext(CounterContext);
  /* Render logic ... */
}

When CounterContext.jsx is modified, the counter and setCounter inside MyComponent.jsx are assigned the default context values and you can see it printing "this is default value" on calling setCounter.

piyushere avatar Dec 09 '21 14:12 piyushere

@selrond Is there a solution to this problem?

iamhmx avatar Feb 21 '22 12:02 iamhmx

While attempting to migrate an app that uses context quite a bit, this has blocked me from moving forward as well. It would be great to get an update on whether or not this is going to be worked on. It seems to happen when the createContext hook is in the same file as the component where the Provider is used. In my case, I don't have a clean way to insert it on a parent component without wrapping my entire app where the routes are, which is not something I wish to do. I found a related issue in Snowpack, which also uses esbuild to do HMR.

nthurnau avatar Mar 18 '22 17:03 nthurnau

I am also facing the same issue and indeed it's related to Vite or @vitejs/plugin-react. I have tried to run the same application using the react-scripts package (used by create-react-app) and I was able to preserve the state even after modifying the context provider.

Wondering if it's because Vite HMR triggers component reload (resetting any previous state) on modifying initial state values:

// CounterContext.jsx
const CounterContext = createContext({
  counter: 0,
  setCounter: (value) => { console.log('this is default value') }
})
/* CouterProvider ... */


// MyComponent.jsx
const MyComponent = (props) => {
  const { counter, setCounter } = useContext(CounterContext);
  /* Render logic ... */
}

When CounterContext.jsx is modified, the counter and setCounter inside MyComponent.jsx are assigned the default context values and you can see it printing "this is default value" on calling setCounter.

Having the same issue.

arifszn avatar Mar 19 '22 10:03 arifszn

Any updates on this? I have the same issue as well

tekclawltd avatar Mar 23 '22 03:03 tekclawltd

Same issue here, but im trying to be more specific with that.

In my experience:

  1. IN THE SAME FILE. Creates a context, and exports a provider
export const Context = createContext();

export const Provider = ({children}) => (
  <Context.Provider value={/* whatever*/}>{children}</Context.Provider>
)

Running npm run dev works. Forcing a hot reload on this file, crashes (context gets default value for all useContext calls).

  1. IN SEPARATED FILES
export const Context = createContext();
import { Context } from './my-context-file';

export const Provider = ({children}) => (
  <Context.Provider value={/* whatever*/}>{children}</Context.Provider>
)

Running npm run dev works. Forcing a hot reload on any of those files, still works.

Tell me if you need more info, or a minium reproduction repo.

Hope it helps!

pablocdev avatar Mar 27 '22 22:03 pablocdev

I'm also stuck with issue related to Context and HMR/fast refresh. In my case createContext and Provider are in separated files. If I modify a component with useContext, HMR reloads my component but I get an error which means that useContext return default value (the value in createContext: export const MyContext = createContext({ a: "foo"})) but I need value from Provider (<MyContext.Provider value={b: "bar"}>)

To workaround this issue, I just use { fastRefresh: false } option in @vitejs/plugin-react which is clearly disappointing when you are used { fastRefresh: true } ;)

stregouet avatar Mar 28 '22 07:03 stregouet

In my case, I was trying to add a MobX store with default provider:

export class Store implements StoreSchema {
  commonStore = new CommonStore();
  userStore = new UserStore();
}

export const store = new Store();

export const StoreContext = createContext(store);

export const useStore = () => {
  return useContext(StoreContext);
};

then calling it in the main.tsx:

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <StoreContext.Provider value={store}> {/* */}
      <CustomRouter history={history}>
        <App />
      </CustomRouter>
    </StoreContext.Provider>
  </React.StrictMode>
);

It works at first, but after any change, (whether in context, component, style, etc.), and hot reload, I get a white screen with this error in console:

Uncaught ReferenceError: can't access lexical declaration 'StoreContext' before initialization

Update - Creating context and provider in one file partially solves the problem.

However, still editing inside the context itself break the code and require page refresh.

Here is what I tried:

interface StoreSchema {
  commonStore: CommonStore;
  userStore: UserStore;
}

export class Store implements StoreSchema {
  commonStore = new CommonStore();
  userStore = new UserStore();
}

export const store = new Store();

const storeContext = createContext(store);

export const StoreContextProvider = (props: { children: ReactNode | JSX.Element }) => {
  return <storeContext.Provider value={store}>{props.children}</storeContext.Provider>;
};

export const useStore = () => {
  return useContext(storeContext);
};

and only call the StoreContextProvider in main.tsx:

let reactRoot;

// to avoid react console warning: root is already defined.
if (!reactRoot) {
  const root = document.getElementById("root");
  if (root) { // to avoid eslint warning on using non-null assertion
    reactRoot = ReactDOM.createRoot(root);
  } else {
    console.error("No root element is defined in the index file.");
  }
}

reactRoot?.render(
  <React.StrictMode>
    <StoreContextProvider>
      <CustomRouter history={history}>
        <App />
      </CustomRouter>
    </StoreContextProvider>
  </React.StrictMode>
);

adnanalbeda avatar May 23 '22 06:05 adnanalbeda

Same issue here, but im trying to be more specific with that.

In my experience:

  1. IN THE SAME FILE. Creates a context, and exports a provider
export const Context = createContext();

export const Provider = ({children}) => (
  <Context.Provider value={/* whatever*/}>{children}</Context.Provider>
)

Running npm run dev works. Forcing a hot reload on this file, crashes (context gets default value for all useContext calls).

  1. IN SEPARATED FILES
export const Context = createContext();
import { Context } from './my-context-file';

export const Provider = ({children}) => (
  <Context.Provider value={/* whatever*/}>{children}</Context.Provider>
)

Running npm run dev works. Forcing a hot reload on any of those files, still works.

Tell me if you need more info, or a minium reproduction repo.

Hope it helps!

I tried the 2nd option, though it does work with HMR. We kind-a lose the capability of Fast Refresh. While fast refresh is still buggy, I think disabling it for now is the only workaround ☹️: https://github.com/vitejs/vite/issues/3301#issuecomment-1080292430

iceniveth avatar Jun 11 '22 06:06 iceniveth

I finally find a trick to make it works => implement my own vite plugin which automatically wraps createContext call with a lookup in local cache I found this trick in jotai repo

stregouet avatar Jun 13 '22 12:06 stregouet

@stregouet This sounds really promising - would you be able to share your plugin? I have a repo that has turned into a bit of a nightmare to work on because of this issue.

kalda341 avatar Jun 13 '22 21:06 kalda341

@kalda341 I didn't publish it anywhere because it is not really robust and a bit too specific to my case.

anyway here is the code:

import type { Plugin, ResolvedConfig } from "vite";

export default function cacheContext() {
    let config: ResolvedConfig;
    const p: Plugin = {
        name: "myorg:cache-context",
        configResolved(resolvedConfig) {
            // store the resolved config
            config = resolvedConfig;
        },
        transform(src, id) {
            if (config.command !== "serve") {
                // not in dev mode
                return;
            }
           // this if allow me to speed up plugin process, since all of my context 
           // are located in `context` directory
            if (!/mypkg\/src\/context/.test(id)) {
                return;
            }
            return {
                code:
                    `
window.contextCache = window.contextCache || {
  cache: new Map(),
  get(key, inst) {
    if (this.cache.has(key)) {
      return this.cache.get(key);
    }
    this.cache.set(key, inst);
    return inst;
  }
};
` +
                    src.replace(
                        /export const ([^=]*)=\s*React.createContext\(([^;]*);/g,
                        (_, name: string, value: string) =>
                            `export const ${name} = ` +
                            `window.contextCache.get("${name.trim()}", React.createContext(${value});`,
                    ),
                map: null,
            };
        },
    };
    return p;
}

you see, it is regex based, but to manipulate code it is better and more robust to use something like babel (as jotai did)

stregouet avatar Jun 14 '22 13:06 stregouet

same issue here, export useContext will be break when hot reload... feeling weird, and im still trying figure out the solution...

I'm also stuck with issue related to Context and HMR/fast refresh. In my case createContext and Provider are in separated files. If I modify a component with useContext, HMR reloads my component but I get an error which means that useContext return default value (the value in createContext: export const MyContext = createContext({ a: "foo"})) but I need value from Provider (<MyContext.Provider value={b: "bar"}>)

To workaround this issue, I just use { fastRefresh: false } option in @vitejs/plugin-react which is clearly disappointing when you are used { fastRefresh: true } ;)

i tried this, wont work... I still had to manually refresh everytime when i save work on my vite project tho.

Could anyone tell me which old version did not have this problem, i wish to downgrade temporary to continue work with my project smoothly...

image

I saw this everytime i save the code (Tony's bug)

@sodatea can we mark it as major bug?

I found an temporary solution which is downgrade the vite version to v2.5.0 and use @vitejs/plugin-react-refresh instead of @vitejs/plugin-react should works without the Tony's bug.

image

liho00 avatar Jul 01 '22 17:07 liho00

Vite 3 is out, but React context still do not support HMR

wenerme avatar Jul 13 '22 14:07 wenerme

Vite 3 is out, but React context still do not support HMR

Some correction, Vite 3 is out, however Vite 3's HMR still didn't support React Context yet.

Your welcome!

Hence I would not recommend react developers using vite as development tool. Hence, u see Nextjs, ionic, and bunch of huge react frameworks still did not using vite yet. Nextjs, Webpack are much more mature for react development, however vite is more focus on vue 3 based on my understanding 👍

Some add on, React Context is great feature, without React context, We cant build a scalable and huge react project with ease!

For those thumb down reaction, please share your thought and tell me am i say something wrong? Please correct me if im wrong, Thanks!

liho98 avatar Jul 13 '22 15:07 liho98

I have this same issue. Whenever I modify the context, I usually have to a manual page reload to fix it.

I thought it'd be cool if vite had a feature where I could add a comment to the top of a file like // @vite full-page-reload

I could put at the top of a react context file to do page reload instead of HMR when I edit that file.

seeker-3 avatar Jul 19 '22 08:07 seeker-3

I have this same issue. Whenever I modify the context, I usually have to a manual page reload to fix it.

I thought it'd be cool if vite had a feature where I could add a comment to the top of a file like // @vite full-page-reload

I could put at the top of a react context file to do page reload instead of HMR when I edit that file.

@seeker-3 I believe you could use the HMR API to achieve the same: https://vitejs.dev/guide/api-hmr.html#hot-decline

AndrewLeedham avatar Jul 19 '22 08:07 AndrewLeedham

Hence I would not recommend react developers using vite as development tool. Hence, u see Nextjs, ionic, and bunch of huge react frameworks still did not using vite yet.

We have been using CRA with typescript from version 2.X to 4.X. In the span of three years our codebase grew a lot. Starting our application locally takes more than a minute. Building it is even slower in CI, more than 15 minutes. Hot reloading also took more than five seconds.

Migrating to Nextjs would require a lot of refactorings on our end since we heavily rely on React context as global state management. And it's routing is based on file system which makes it complicated to migrate our app.

After migrating CRA to Vite we've managed to speed up starting our application from more than a minute to instantaneous (less than a second). And the build took less than 9 minutes (more than 15 minutes before). ~Hot reloading~ Live reload is also fast.

iceniveth avatar Jul 19 '22 09:07 iceniveth

Hence I would not recommend react developers using vite as development tool. Hence, u see Nextjs, ionic, and bunch of huge react frameworks still did not using vite yet.

We have been using CRA with typescript from version 3.X to 4.X. In the span of three years our codebase grew a lot. Starting our application locally takes more than a minute. Building it is even slower in CI, more than 15 minutes. Hot reloading also took more than five seconds.

Migrating to Nextjs would require a lot of refactorings on our end since we heavily rely on React context as global state management. And it's routing is based on file system which makes it complicated to migrate our app.

After migrating CRA to Vite we've managed to speed up starting our application from more than a minute to instantaneous (less than a second). And the build took less than 9 minutes (more than 15 minutes before). Hot reloading is also fast.

weird tho, vite does not support react context when hot reloading, how could you development your app, manually refreshing the page/app every time after hot reload? dont you feel troublesome when developing react app with this behavior?

liho98 avatar Jul 19 '22 09:07 liho98

how could you development your app, manually refreshing the page/app every time after hot reload

Ours automatically reloads whenever a file has been saved.

There are a lot of guides roaming around. Like this one: https://www.darraghoriordan.com/2021/05/16/migrating-from-create-react-app-to-vite/

Though we have a lot of dependencies issues but we managed to resolve them all.

iceniveth avatar Jul 19 '22 09:07 iceniveth

how could you development your app, manually refreshing the page/app every time after hot reload

Ours automatically reloads whenever a file has been saved.

thats not hot reload man haha image

reference: https://www.geeksforgeeks.org/difference-between-hot-reloading-and-live-reloading-in-react-native/

liho98 avatar Jul 19 '22 09:07 liho98

how could you development your app, manually refreshing the page/app every time after hot reload

Ours automatically reloads whenever a file has been saved.

thats not hot reload man haha image

reference: https://www.geeksforgeeks.org/difference-between-hot-reloading-and-live-reloading-in-react-native/

Got it, I stand corrected. It's live reloading for our case.

iceniveth avatar Jul 19 '22 09:07 iceniveth

how could you development your app, manually refreshing the page/app every time after hot reload

Ours automatically reloads whenever a file has been saved.

thats not hot reload man haha image reference: https://www.geeksforgeeks.org/difference-between-hot-reloading-and-live-reloading-in-react-native/

Got it, I stand corrected. It's live reloading for our case.

hence, thats why this issue arise, HMR (hot module reload) vite do not support react context, live reloading does not help much in this issue. the title of this issue declared, if your app live reloading every changes, the state of the app keep refreshed/flushed everytime...

liho98 avatar Jul 19 '22 09:07 liho98

To workaround, add this plugin:

const preserveRefPlugin = () => {
  const preverseRefFunc = `
function __preserveRef(key, v) {
  if (import.meta.env.PROD) return v;

  import.meta.hot.data ??= {}
  import.meta.hot.data.contexts ??= {}
  const old = import.meta.hot.data.contexts[key];
  const now = old || v;

  import.meta.hot.on('vite:beforeUpdate', () => {
    import.meta.hot.data.contexts[key] = now;
  });

  return now;
}
`;

  return {
    name: 'preserveRef',
    transform(code) {
      if (!code.includes('__preserveRef')) return;

      return {
        code: code + preverseRefFunc,
        map: null,
      };
    },
  };
};

and then, wrap export const CountContext = React.createContext() with __preserveRef() like export const CountContext = __preserveRef('CountContext', React.createContext());.

stackblitz example

sapphi-red avatar Jul 21 '22 14:07 sapphi-red

To workaround, add this plugin:

const preserveRefPlugin = () => {
  const preverseRefFunc = `
function __preserveRef(key, v) {
  if (import.meta.env.PROD) return v;

  import.meta.hot.data ??= {}
  import.meta.hot.data.contexts ??= {}
  const old = import.meta.hot.data.contexts[key];
  const now = old || v;

  import.meta.hot.on('vite:beforeUpdate', () => {
    import.meta.hot.data.contexts[key] = now;
  });

  return now;
}
`;

  return {
    name: 'preserveRef',
    transform(code) {
      if (!code.includes('__preserveRef')) return;

      return {
        code: code + preverseRefFunc,
        map: null,
      };
    },
  };
};

and then, wrap export const CountContext = React.createContext() with __preserveRef() like export const CountContext = __preserveRef('CountContext', React.createContext());.

stackblitz example

If this works, then I think it's possible to do a more complete implementation for it, so it can be more generic and less needing for calling __preserveRef.

for example, we can change the condition here if (!code.includes('__preserveRef')) return; into a better regex function where it looks for create context:

/(.*createContext.*"react")|(React\.createContext)/

then replace createContext where it's called with a line that call the plugin function.

/(const|let) (.*) = (createContext.*\(.*\))/ 
//  this matching any here        ||| is for in case of using typing.

and the replacement is:

($1) ($2) = __preserveRef(($2),($3));

There might be some issues with this, like someone choose to export createContext in a different name, but if some does that, I doubt they are doing a good thing.

So what do you think?

adnanalbeda avatar Jul 21 '22 19:07 adnanalbeda

@adnanalbeda Your suggestion works well, as long as it's a safe assumption that the context is only created once within the lifetime of the app (which is the majority of cases, but not all).

I should also point out that this preserveRef solution does not handle nesting of the same context within itself. For example, this case would fail:

<MyContext.Provider value={{ foo: "level one" }}>
  <SomethingA />
  <MyContext.Provider value={{ foo: "level two" }}>
    <SomethingB />
  </MyContext.Provider>
</MyContext.Provider>

SomethingA should have value "level one", whereas SomethingB should have value "level two". The preserveRef solution would force "level two" on both upon hot reload. Granted, this is not a common pattern (that I am aware of), but it's important to note.

justinbhopper avatar Jul 21 '22 19:07 justinbhopper

AFAIK, Providing a value in Provider will replace any value assigned before for the next children only.

So SomethingA should always get what is provided in the first level, cause even that provider will replace any default value passed when creating the context.

And SomethingB will get whatever provided in the second level.

If what you're saying is true, this this won't solve the issue anyway if the original context has no default value and no provider is made.

Because the preserved context value is null.

adnanalbeda avatar Jul 21 '22 19:07 adnanalbeda

@adnanalbeda Yes correct, SomethingA gets what is provided at the first level and SomethingB gets provided at the second level. If we use the preserveRef plugin, then both providers would have the same key, because in my example the key would be "MyContext". Thus, they would both be in contention when writing to import.meta.hot.data.contexts["MyContext"].

Again, nesting providers of the same context is not a popular thing to do, but I'm just pointing out the limitation to keying the context data.

justinbhopper avatar Jul 21 '22 20:07 justinbhopper

I think, in this case, it should always get the same context with the default value.

The main issue is on HMR, the old context is removed, not the provider. so keeping a reference should solve the issue because we're telling it, hey, here is the context you used for this provider.

But the part which I'm not sure of, is that each provider maybe has it's own mapping for values, based on the reference of the context.

I will test it tomorrow hopefully and see what will happen.

adnanalbeda avatar Jul 21 '22 20:07 adnanalbeda

@adnanalbeda Yes correct, SomethingA gets what is provided at the first level and SomethingB gets provided at the second level. If we use the preserveRef plugin, then both providers would have the same key, because in my example the key would be "MyContext". Thus, they would both be in contention when writing to import.meta.hot.data.contexts["MyContext"].

Again, nesting providers of the same context is not a popular thing to do, but I'm just pointing out the limitation to keying the context data.

This is the kind of edge case behaviour that would probably affect very few people but those affected would likely be driven to the brink of insanity before they figured out what was causing it 😅

SeanRoberts avatar Jul 21 '22 20:07 SeanRoberts

This is the kind of edge case behaviour that would probably affect very few people but those affected would likely be driven to the brink of insanity before they figured out what was causing it 😅

Agreed. 🙂 I think most here would love this plugin once its written, because it's basically plug-and-play and handles 99% of use cases.

justinbhopper avatar Jul 21 '22 20:07 justinbhopper

@justinbhopper It works so smooth with no issues. HMR don't break the provider anymore.

Here's the steps:

1- create a new vite react ts project. 2- inside App.tsx, add this code:

import { Children, createContext, useContext, useState } from 'react'
import logo from './logo.svg'
import './App.css'

const Context = createContext({ hello: "default context" });

function LoadContextData(props: { children?: React.ReactNode }) {
  const context = useContext(Context);
  return <>
    <div>
      {context.hello}
      {props.children}
    </div>
  </>
}

function App() {
  const [count, setCount] = useState(0)
  const context = useContext(Context); // No providers - should show the default value
  return (
    <div className="App">
      {context.hello}
      <Context.Provider value={{ hello: "first level provider" }}>
        <LoadContextData>
          <Context.Provider value={{ hello: "second level provider" }}>
            <LoadContextData>
              <div>
                Edit Here, it works perfectly well.
              </div>
            </LoadContextData>
          </Context.Provider>
        </LoadContextData>
      </Context.Provider>
    </div>
  )
}

export default App

3- Inside vite.config.ts, add this plugin:

const reactContextHmr = () => {
  const preverseRefFunc = `
  function __preserveRef(key, v) {
    if (import.meta.env.PROD) return v;
    
  import.meta.hot.data ??= {}
  import.meta.hot.data.contexts ??= {}
  const old = import.meta.hot.data.contexts[key];
  const now = old || v;
  
  import.meta.hot.on('vite:beforeUpdate', () => {
    import.meta.hot.data.contexts[key] = now;
  });
  
  return now;
}
`;
  const createContextRegEx = /(import.*createContext.*react)|(React.createContext.*\(.*\))/;
  const oldCreateRegex = /(const|let) (.*) = ((React\.createContext.*)|(createContext.*));/;
  const newCreateRegex = "$1 $2 = __preserveRef(\"$2\",$3);";

  return {
    name: 'preserveRef',
    transform(code: string) {
      if (!code.match(createContextRegEx)) return;

      return {
        code: (code + preverseRefFunc).replace(oldCreateRegex, newCreateRegex),
        map: null,
      };
    },
  };
};

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), reactContextHmr()]
})

And it works.

Now we just need some better regex matching rules.


Update 1:

Tried this with context in a separated file, it doesn't work.

adnanalbeda avatar Jul 21 '22 21:07 adnanalbeda

@adnanalbeda

If this works, then I think it's possible to do a more complete implementation for it, so it can be more generic and less needing for calling __preserveRef.

Yes, it's possble. For now, I wanted to avoid caveats while using the plugin. The best way is to write a babel transform, but it requires some work and I wanted to check if this works for people.

Tried this with context in a separated file, it doesn't work.

Do you have a reproduction of this one?


@justinbhopper

I should also point out that this preserveRef solution does not handle nesting of the same context within itself.

I didn't think of that, but it seems it's working well? (stackblitz)

sapphi-red avatar Jul 22 '22 02:07 sapphi-red

@sapphi-red your method works perfectly well, with nested level, separated files and everything. I tried editing in CounterPovider, Counter, and App. All are good, and works with the expected normal behavior.

adnanalbeda avatar Jul 22 '22 07:07 adnanalbeda

To workaround, add this plugin:

Worked like a charm for our usecase on a medium-large project. Thank you @sapphi-red

brunopuzoni avatar Jul 22 '22 13:07 brunopuzoni

Can someone confirm this?

I just ran [email protected] with vitejs/[email protected], got the whole code from this chad @sapphi-red , but without the plugin. and the __preserveRef.

Then tried to edit the context, counter, app, etc. and all worked with no issues.

adnanalbeda avatar Jul 22 '22 14:07 adnanalbeda

@adnanalbeda I also cannot reproduce the bug using the original repo (https://github.com/selrond/vite-react-usecontext) after upgrading it to [email protected] and @vitejs/[email protected]. Is this bug still occurring in latest?

https://stackblitz.com/edit/github-6861mq-hcsekc

justinbhopper avatar Jul 22 '22 14:07 justinbhopper

Great, so the issue is resolved, and no one knows why.

adnanalbeda avatar Jul 22 '22 14:07 adnanalbeda

It still happens for me. Open src/CountProvider.jsx and edit that file (not other files). https://stackblitz.com/edit/github-6861mq-6f4ezn?file=src%2FCountProvider.jsx

sapphi-red avatar Jul 22 '22 14:07 sapphi-red

@sapphi-red , are you trying it locally? or just on stackblitz?

adnanalbeda avatar Jul 22 '22 14:07 adnanalbeda

I was trying on stackblitz. But I tried it now with local and it happened.

sapphi-red avatar Jul 22 '22 14:07 sapphi-red

Thanks, now I know what's going on.

DON'T define the context with its provider in the SAME FILE.

HMR will break if any change happen in that file, or any file that is used in the context or the provider.

More about it.

If we have one file with this code:

import React from 'react';

export const CountContext =
    // __preserveRef("CountContext", React.createContext<any>(null));
    React.createContext<any>(null)
    ;

export const CountContextProvider = (props: { children: React.ReactNode }) => {
    const [count, setCount] = React.useState(1);
    return (<CountContext.Provider value={{ count, setCount }}>
        {props.children}
    </CountContext.Provider>)
}

Any change here, HMR will break.

But separate them into two files:

// CountContext(.ts or .tsx)
import React from 'react';

export const CountContext =
    // __preserveRef("CountContext", React.createContext<any>(null));
    React.createContext<any>(null)
    ;

and

// CountContext.tsx
import React from "react";
import { CountContext } from "./CountContext";

export const CountContextProvider = (props: { children: React.ReactNode }) => {
    const [count, setCount] = React.useState(1);
    return (<CountContext.Provider value={{ count, setCount }}>
        {props.children}
    </CountContext.Provider>)
}

In this way, the issue is resolved, and changes can happen safely everywhere.


I will test it next with a simple mobx store and see what happens.


Update:

After adding a small mobx store, I can confirm, this issue only happens when both context and provider are defined in the same file. Now we need to know, why?

adnanalbeda avatar Jul 22 '22 14:07 adnanalbeda

The same issue with provide/inject in Vue. When editing any component in the tree that shares the state provided in the root, provided state and key are lost after the hot reload, emitting an error injection "Symbol(auth-state)" not found. Reproducing both in Vite 2 and Vite 3.

Geloosa avatar Jul 26 '22 09:07 Geloosa