Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

HMR error: Cannot access '...' before initialization #3033

Closed
6 tasks done
edikdeisling opened this issue Apr 17, 2021 · 68 comments · Fixed by #13024 or #14867
Closed
6 tasks done

HMR error: Cannot access '...' before initialization #3033

edikdeisling opened this issue Apr 17, 2021 · 68 comments · Fixed by #13024 or #14867

Comments

@edikdeisling
Copy link
Contributor

edikdeisling commented Apr 17, 2021

Describe the bug

The error happens when I try to edit component, that

  1. Wrap in connect function (redux)
  2. Is inside dependency loop
  3. There is another component inside dependency loop

Seems weird, but it's not so rare case when project uses routers like UiRouter or Universal router

I expect that component will be updated with HMR without errors or may be reload the page but not to throw an error

Reproduction

Repo: https://github.com/xSorc/test-vite-fast-refresh-loop-dependency

To reproduce this error you need to open the project and try to edit Component.tsx. You will see an error
image

System Info

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

  System:
    OS: Windows 10 10.0.19042
    CPU: (6) x64 Intel(R) Core(TM) i5-8600K CPU @ 3.60GHz
    Memory: 8.19 GB / 15.94 GB
  Binaries:
    Node: 14.15.3 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.10 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 6.14.9 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.19041.423.0), Chromium (89.0.774.77)
    Internet Explorer: 11.0.19041.1
  npmPackages:
    vite: ^2.1.5 => 2.1.5

Used package manager:

Logs


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

@ygj6
Copy link
Member

ygj6 commented Apr 21, 2021

@xSorc Hello ~ I read your description and repeated the problem, I guess it might be the React Render problem
image

@edikdeisling
Copy link
Contributor Author

edikdeisling commented Apr 22, 2021

@ygj6 Hello. Why do you think that it is react problem? I think react only calls render fn.
I don't have whole picture, but here some thoughts:

We can't access variable from module, that already depend on us. But this app works - because we access variable not instantly(during module loading), but during render.
If we write smth like this - app will fall instantly

import { STATES_CONST } from './states';

console.log(STATES_CONST); // this line break all because we try access STATES_CONST during module loading

class Component extends PureComponent {
    render() {
        return <div>States const: {STATES_CONST}</div>;
    }
}

maybe this issue has to be fixed like this(this is Vite code)?

try {
    const newMod = await import(
    /* @vite-ignore */
    base +
        path.slice(1) +
        `?import&t=${timestamp}${query ? `&${query}` : ''}`);
    moduleMap.set(dep, newMod);
}
catch (e) {
    warnFailedFetch(e, dep);
    location.reload(); // I was added this line
}

P.S. this hmr fails because during loading of dependencies of Component.tsx after hmr there is situation that ComponentOther.tsx loads before router.ts. And router.ts access variable during module loading(this is forbidden)
image

maybe it's project problem, but I think Vite should reload the page in this case?

@patak-dev
Copy link
Member

I think there is a bug in plugin-react-refresh, but we can not reload on error. Check out the warning message "This could be due to syntax errors or importing non-existent modules.". When the user is typing and there is an error, we want to wait until a successful run to update the page with HMR, or the state will be lost after the full reload while the user is typing (for example if it is using auto save)

@edikdeisling
Copy link
Contributor Author

edikdeisling commented Apr 25, 2021

@patak-js how about this idea?

We can't update this module because of imports loop, but we can prevent our page to be broken. Now every module call performReactRefresh during loading. We can change it and call react refresh only after successful import. Something like this:

current code here: plugin-react-refresh/index.js

const runtimeCode = `
const exports = {}
${fs.readFileSync(runtimeFilePath, 'utf-8')}
let queue = false;
exports.queueReactRefresh = () => (queue = true); // do not debounce. Only set flag
window.flushReactRefresh = () => { // set window fn to flush updates. We call them *after* successful import 
  if (queue) {
    exports.performReactRefresh();
    queue = false;
  }
}
export default exports

...

    const footer = `
if (import.meta.hot) {
  window.$RefreshReg$ = prevRefreshReg;
  window.$RefreshSig$ = prevRefreshSig;
  ${
    isReasonReact || isRefreshBoundary(result.ast)
      ? `import.meta.hot.accept();`
      : ``
  }
  RefreshRuntime.queueReactRefresh(); // call here queue instead of debounce performReactRefresh
}`

current code here: vite/dist/client/client.js

try {
    const newMod = await import(
    /* @vite-ignore */
    base +
        path.slice(1) +
        `?import&t=${timestamp}${query ? `&${query}` : ''}`);
    window.flushReactRefresh?.(); // flush updates after successful import
    moduleMap.set(dep, newMod);
}
catch (e) {
    warnFailedFetch(e, dep);
}

There is another option how to fix this by one line. We can make something like this:

try {
    const newMod = await import(
    /* @vite-ignore */
    base +
        path.slice(1) +
        `?import&t=${timestamp}${query ? `&${query}` : ''}`);
    moduleMap.set(dep, newMod);
}
catch (e) {
    clearTimeout(window.__vite_plugin_react_timeout); // I added this line
    warnFailedFetch(e, dep);
}

@edikdeisling
Copy link
Contributor Author

I understood that we can't write code connected to react inside Vite client... Needs more complex solution

@flasco

This comment was marked as spam.

@wellfrog16
Copy link

I have the same question when i use store and axios in route beforeEach

@wen81643956

This comment was marked as duplicate.

@pasBone

This comment was marked as spam.

3 similar comments
@Akimotorakiyu

This comment was marked as spam.

@NomadBin

This comment was marked as spam.

@etedesco

This comment was marked as spam.

@tzusman
Copy link

tzusman commented Aug 25, 2021

@xSorc were you able to identify the source of this issue? I'm sure there are thousands of Vite apps with react and connected components, so there must be something specific that is causing this issue. I've had to turn off HMR for the app to load properly.

@edikdeisling
Copy link
Contributor Author

edikdeisling commented Aug 27, 2021

@tzusman I can't understand the whole picture. All I know that the problem is caused by imports loop.
For example you have this structure
image

That works during first load. But when I tried to change Component 1, an error Cannot access 'STATES' before initialization happend.
The most strange thing is if I change in Component 1 line

function connect(Cmp: ComponentType) {
   return Cmp
}

class Component extends PureComponent {
   render() {
       return <div>States const: {STATES_CONST}</div>;
   }
}

export default connect(Component);

to lines

function connect(Cmp: ComponentType) {
    return Cmp
}

class Component extends PureComponent {
    render() {
        return <div>States const: {STATES_CONST}</div>;
    }
}

const a = connect(Component);
export default a;

the problem is gone. I have no idea how this works

The structure may be simplified to this
image
so it may be different

My advice is to remove a dependency loop. I've done it in my project. This is the easiest way to handle it right now

@Oungseik
Copy link

This also happen to me on development. Build and serve is work well.
Screenshot_2021-09-16_17-42-16

@matt-erhart

This comment was marked as spam.

@nawbc

This comment was marked as spam.

@matt-erhart
Copy link

Stumbled onto some tips from parceljs that may help with this issue:

  • Avoid class components – Fast Refresh only works with function components (and Hooks).
  • Export only React components – If a file exports a mix of React components and other types of values, its state will be reset whenever it changes. To preserve state, only export React components and move other exports to a different file if possible.
  • Avoid unnamed default exports – Declaring components using a default exported arrow function will cause state to be reset when it is changed. Use a named function, or assign the arrow function to a variable instead.
  • Keep entry components in their own files – Entry components should be in a separate file from the one that calls ReactDOM.render or they will be remounted on every change.
    For more tips, see the official React Fast Refresh docs. https://reactnative.dev/docs/fast-refresh

I actually was getting a hard refresh way more than I was expecting before following each of those tips, now I'm seeing fast refresh everywhere and haven't seen that error yet.

@Dodd2013

This comment was marked as spam.

@jsefiani
Copy link

@matt-erhart I had the same issue but your last tip really helped me resolve the issue, thank you so much!

What I had:

//main.tsx
const AppProviders: React.FC = (props) => {...}

ReactDOM.render(
  <React.StrictMode>
    <AppProviders>
      <App />
    </AppProviders>
  </React.StrictMode>,
  document.getElementById('root')
);

Changed it to:

// AppProviders.tsx
const AppProviders: React.FC = (props) => {...}

// main.tsx
ReactDOM.render(
  <React.StrictMode>
    <AppProviders>
      <App />
    </AppProviders>
  </React.StrictMode>,
  document.getElementById('root')
);

So I just moved AppProviders to a separate file and everything started working. Thanks again!

@michaeljohansen
Copy link

For future travelers: If you see the ReferenceError: Cannot access (…) before initialization error then you may have circular dependencies that need to be resolved. Discover them with a tool like Madge: madge --circular <path>

@Niputi
Copy link
Contributor

Niputi commented Nov 19, 2021

vs code extension to help find circular dependencies https://marketplace.visualstudio.com/items?itemName=iulian-radu-at.find-unused-exports

source https://twitter.com/alecdotbiz/status/1461729042604998664?t=MJApPe1FxTzI6zMMXXSktQ&s=19

@semmy2010

This comment was marked as spam.

@wanghuajin-bello

This comment was marked as spam.

1 similar comment
@mokus

This comment was marked as spam.

@SuspiciousLookingOwl
Copy link

I found this solution which prevents HMR from breaking if there are circular dependencies, but I'm not sure what are the drawbacks

// vite.config.ts

export default defineConfig({
  plugins: [
    // your plugins,
    {
      name: "singleHMR",
      handleHotUpdate({ modules }) {
        modules.map((m) => {
          m.importedModules = new Set();
          m.importers = new Set();
        });

        return modules;
      },
    },
  ],
});

@marlosirapuan
Copy link

I found this solution which prevents HMR from breaking if there are circular dependencies, but I'm not sure what are the drawbacks

// vite.config.ts

export default defineConfig({
  plugins: [
    // your plugins,
    {
      name: "singleHMR",
      handleHotUpdate({ modules }) {
        modules.map((m) => {
          m.importedModules = new Set();
          m.importers = new Set();
        });

        return modules;
      },
    },
  ],
});

woow! In my project I was having problems using use-sound inside a context and this configuration resolved! Thanks!!

@enodr
Copy link

enodr commented Dec 29, 2022

    {
      name: "singleHMR",
      handleHotUpdate({ modules }) {
        modules.map((m) => {
          m.importedModules = new Set();
          m.importers = new Set();
        });

        return modules;
      },
    },

You saved my day. We have a huge vue3 project and were stuck with vite 2. vite 3 and vite 4 both had the HMR issue described here, but this fix works perfectly. Thanks again.

@linxianxi
Copy link

same. vite@4.0.4

@XinChou16
Copy link

same

@NuclleaR
Copy link

I had the same. My issue was this case
https://github.com/vitejs/vite-plugin-react-swc#consistent-components-exports

@ThomasKientz
Copy link

STOP commenting “same” and upvote first comment please. Avoid this all over GitHub, your are sending useless notifications to everyone who had subscribed to the issue. Thanks.

@ZhipengYang0605
Copy link

ZhipengYang0605 commented Feb 6, 2023

{
 name: "singleHMR",
 handleHotUpdate({ modules }) {
   modules.map((m) => {
     m.importedModules = new Set();
     m.importers = new Set();
   });
   return modules;
 },
}

加了这段代码确实热更新不再报错了,但是每次更改样式的时候不会刷新。我看了下文档,你这个方法在vite3x才支持,vite2x目前还没有,我觉得可能是我的vite版本低的问题
vue:3.2.25
vite:2.8.0

@ZhipengYang0605
Copy link

比如A import B,B import C,这样是顺序依赖,是没有问题的。但是如果C 再import A 就出现循环引用了,因为A通过B间接引用了C。 所以工具无法检测出来的时候自己认真排查一下,就是这个原因。

多谢大佬,检查了下代码,发现了问题!

@avegancafe
Copy link

@SuspiciousLookingOwl did you ever find any negatives to doing that? I'm also seeing that this is fixing it for me, but unsure if it also breaks anything

@avegancafe
Copy link

Anyone have any success finding the circular dependencies when madge doesn't think there are any?

@ijandc
Copy link

ijandc commented Feb 16, 2023

same, vite@4.1.1

@crummy
Copy link

crummy commented Feb 24, 2023

Thanks @michaeljohansen!

For future travelers: If you see the ReferenceError: Cannot access (…) before initialization error then you may have circular dependencies that need to be resolved. Discover them with a tool like Madge: madge --circular

Here's how I did it:

npx madge src/index.tsx --circular

This pointed me to entries like:

3) index.tsx > Foo.tsx

Foo.tsx had imported Context from index.tsx.

I fixed it by moving Context into its own Context.tsx file and importing it into index.tsx and Foo.tsx.

@xiao-xiaozi
Copy link

same, vite@4.1.4

@sleeyax
Copy link

sleeyax commented Mar 31, 2023

In my case, I had a hidden circular dependency cycle (madge couldn't find it) with react-router and use-react-router-breadcrumbs. I had implemented a custom breadcrumbing system with those two and CRA at the time. I fixed it by upgrading to a newer version of react-router, which now supports breadcrumbs out of the box. By adjusting the breadcrumbs accordingly this issue was solved for me.

This is probably a specific case, but hopefully my findings can be useful to someone in a similar situation!

hyperupcall added a commit to fox-forks/OED that referenced this issue Apr 3, 2023
Vite is more strict the `import`s it accepts. These imports are fixed.

Additionally, the Redux store is now exported form a separate file
(other than `index.tsx`. This was done to workaround
vitejs/vite#3033. The circular imports and
Redux store don't play nice, so the circular import was removed.
@lblblong
Copy link

Is there any progress regarding this issue at present?

@sunnylost
Copy link
Contributor

I have a different perspective on the error you mentioned. While it might not be directly related to the issue you encountered, consider it as a subtle suggestion.

For instance, the code snippet below would trigger a reference error due to the "temporary dead zone". When the bar() function is executed, the const variable foo has not yet been initialized.

bar()

const foo = 'foo'

function bar() {
  console.log(foo)
}

Therefore, to fix this problem, you could try using the vite-plugin-inspect tool to examine the compiled code and check if you're invoking the variable too early, before it has been initialized.

@stoberov
Copy link

stoberov commented May 16, 2023

I had the same. My issue was this case https://github.com/vitejs/vite-plugin-react-swc#consistent-components-exports

Thanks, this pointed me to my own problem - I had a file that exported a few constants AND 1 additional React component. In other words - I had a mix of React and non-React exports from the same file. As soon as I removed the export for the React component (leaving just regular non-React exports) - the error disappeared.

@zhangMrzhang
Copy link

I found this solution which prevents HMR from breaking if there are circular dependencies, but I'm not sure what are the drawbacks

// vite.config.ts

export default defineConfig({
  plugins: [
    // your plugins,
    {
      name: "singleHMR",
      handleHotUpdate({ modules }) {
        modules.map((m) => {
          m.importedModules = new Set();
          m.importers = new Set();
        });

        return modules;
      },
    },
  ],
});

woow! In my project I was having problems using use-sound inside a context and this configuration resolved! Thanks!!

it's useful, thank you very much

@jonasdev
Copy link

I found this solution which prevents HMR from breaking if there are circular dependencies, but I'm not sure what are the drawbacks

// vite.config.ts

export default defineConfig({
  plugins: [
    // your plugins,
    {
      name: "singleHMR",
      handleHotUpdate({ modules }) {
        modules.map((m) => {
          m.importedModules = new Set();
          m.importers = new Set();
        });

        return modules;
      },
    },
  ],
});

Solved the issue about 'Cannot access ...'
But now, when I change a className in a component, I manually have to refresh the page to see the CSS changes. I am using Tailwind.
It hot reloads when I don't use singleHMR as above

@github-actions github-actions bot locked and limited conversation to collaborators Jul 6, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet