vite: HMR breaks when modifying React context provider

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

  • Read the Contributing Guidelines.
  • Read the docs.
  • Check that there isn’t already an issue that reports the same bug to avoid creating a duplicate.
  • Provide a description in this issue that describes the bug.
  • 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.
  • Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 57
  • Comments: 51 (6 by maintainers)

Most upvoted comments

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’ve pushed up a PR that fixes this issue, according to a test I wrote based off the reproduction in the issue description. But, it requires a change to how Vite handles import.meta.hot.invalidate(), so I think it will need some discussion with the Vite team.

@selrond Is there a solution to this problem?

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!

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                               <-- Different
  | updateContainer | @ | react-dom.development.js:25482
  | (anonymous) | @ | react-dom.development.js:26021
  | unbatchedUpdates | @ | react-dom.development.js:22431
  | legacyRenderSubtreeIntoContainer | @ | react-dom.development.js:26020              <-- Until here
  | render | @ | react-dom.development.js:26103
  | main | @ | main.tsx:30
  | async function (async) |   |  
  | main | @ | main.tsx:20
  | (anonymous) | @ | main.tsx:40
------------------------------------------
  | 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
  | (anonymous) | @ | react-dom.development.js:11327                                     <-- Different
  | unstable_runWithPriority | @ | scheduler.development.js:468
  | runWithPriority$1 | @ | react-dom.development.js:11276
  | flushSyncCallbackQueueImpl | @ | react-dom.development.js:11322
  | flushSyncCallbackQueue | @ | react-dom.development.js:11309
  | scheduleUpdateOnFiber | @ | react-dom.development.js:21893
  | updateContainer | @ | react-dom.development.js:25482
  | legacyRenderSubtreeIntoContainer | @ | react-dom.development.js:26037              <-- Until here
  | render | @ | react-dom.development.js:26103
  | main | @ | main.tsx:30
  | async function (async) |   |  
  | main | @ | main.tsx:20
  | (anonymous) | @ | main.tsx:40

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

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?

@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.

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

I noticed the 54 likes, the 47 comments, a few duplicate issues, and the implementation of fastRefresh property in the configuration, so the issue can have a workaround.

The issue doesn’t feel it deserves the p3-minor-bug 🔨 label.

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 } 😉

It’s still a strong reason to choose nextjs instead of vitejs just because of context losing, in real app, a context losing will easily cause a tab 100% CPU because of an error loop, have to keep a task manager open and kill the tab.

Just ran into this issue on a non-vite project. We are introducing context for the first time into our app. After messing around I finally found that yes moving the createContext call to another file resolves the issue mostly. Editing that file still causes the problem.

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

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

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

FYI, we stumbled over this problem too, and are using a (somewhat hacky) solution that seems to work, without needing to write Vite plugins or fiddling with HMR API.

The trick is to move the “createContext” call into the provider component. Using a React ref ensures uniqueness.

What do you think? Does this solution have any downsides I am not aware of (except that this has to be repeated for every context, and goes into production code)?

import { createContext, useContext, useRef } from "react";

let MyContext;

export function MyProvider({ children }) {
    const ref = useRef();
    MyContext = ref.current ??= createContext();
    ...
    return <MyContext.Provider value={...}>{children}</MyContext.Provider>;
}

export default function useMyContext() {
    return useContext(MyContext);
}

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

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

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/

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