vite icon indicating copy to clipboard operation
vite copied to clipboard

the vite's HMR does not work when I use React.lazy() API for lazyload

Open cookiepool opened this issue 4 years ago • 22 comments

Describe the bug

when I use React.lazy(),like this:

// router/index.ts
import React from 'react';

const Home = React.lazy(() => import('../views/Home'));
const About = React.lazy(() => import('../views/About'));

const routes = [
  {
    path: '/',
    exact: true,
    component: Home
  },
  {
    path: '/about',
    exact: true,
    component: About
  },
  {
    path: '/login',
    exact: true,
    component: React.lazy(() => import('../views/login/login'))
  },
  {
    path: '/form-page',
    exact: true,
    component: React.lazy(() => import('../views/form-test/form-page'))
  },
  {
    path: '/props-up',
    exact: true,
    component: React.lazy(() => import('../views/form-test/props-up'))
  }
];

export default routes;

supplement!I use react-router-config manage my routes information,like this:

// App.tsx
import { renderRoutes } from 'react-router-config';
import routes from '@/router';

// other code

render(): JSX.Element {
  return (
    <Router>
      <div className={styles.container}>
        <div className={styles['title-wraper']}>
          <Link to="/">
            <span className={styles['link-title']}>Home</span>
          </Link>
          <span className={styles['link-line']}> | </span>
          <Link to="/about">
            <span className={styles['link-title']}>About</span>
          </Link>
        </div>
        <Switch>
          <React.Suspense fallback={<Loading></Loading>}>
            {renderRoutes(routes)}
          </React.Suspense>
        </Switch>
      </div>
    </Router>
  );
}

when I modify some code in Home.tsx or About.tsx etc,the HMR was not effected.I need refresh the broswer can see my modification.

if I dont use React.lazy() API,the HMR is normal!

Reproduction

I see vite HMR can work,but it nonitor a wrong file. image

image

I modify About.tsx,but vite HMR feed back router/index.ts.

System Info

- OS: win 10
- browser:Chrome 91
- react(react-dom): 17.0.2
- react-router-dom: 5.2.0
- react-router-config: 5.1.1
- vite: 2.4.2

vite.config.ts

import path from 'path';
import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';
import eslintPlugin from 'vite-plugin-eslint';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [reactRefresh(), eslintPlugin()],
  resolve: {
    alias: { '@': path.join(__dirname, 'src') }
  },
  css: {
    modules: {
      generateScopedName: '[name]__[local]--[hash:base64:5]'
    }
  }
});


### Used Package Manager

npm

### Logs

```shell
no error!

Validations

  • [X] Follow our Code of Conduct
  • [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] 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.

cookiepool avatar Jul 18 '21 03:07 cookiepool

I can't reproduce this, can you provide a small repro, thanks

y1d7ng avatar Jul 20 '21 05:07 y1d7ng

Hello @cookiepool. Please provide a minimal reproduction using a GitHub repository or codesandbox. Issues marked with need reproduction will be closed if they have no activity within 3 days.

github-actions[bot] avatar Jul 20 '21 07:07 github-actions[bot]

Same issue, My App is like below code:

const A = lazy(() => import('./pages/A'));
const B = lazy(() => import('./pages/B'));

function App() {
  return (
    <HashRouter>
      <Suspense fallback={<div>loading</div>}>
        <Routes>
          <Route path="/a" element={<A />} />
          <Route path="/b" element={<B />} />
        </Routes>
      </Suspense>
    </HashRouter>
  );
}

After use React.lazy, change in Component A (or B) cannot hot reload, but need switch routing.

songzhj avatar Jul 21 '21 09:07 songzhj

Same issue, My App is like below code:

const A = lazy(() => import('./pages/A'));
const B = lazy(() => import('./pages/B'));

function App() {
  return (
    <HashRouter>
      <Suspense fallback={<div>loading</div>}>
        <Routes>
          <Route path="/a" element={<A />} />
          <Route path="/b" element={<B />} />
        </Routes>
      </Suspense>
    </HashRouter>
  );
}

After use React.lazy, change in Component A (or B) cannot hot reload, but need switch routing.

in path '/a', change Component B to hot reload?

y1d7ng avatar Jul 21 '21 10:07 y1d7ng

maybe you can try this: image if not add this name, i get the same issue with you.

npmrun avatar Jul 24 '21 14:07 npmrun

I can't reproduce this, can you provide a small repro, thanks

https://github.com/cookiepool/vite-react-typescript-template you can use this repository!

cookiepool avatar Jul 28 '21 01:07 cookiepool

Hope your team can fix this soon! My team has the same trouble with this!

harrytran998 avatar Aug 02 '21 00:08 harrytran998

The issue mainly affects Class Component.Function Component is normal!

cookiepool avatar Aug 02 '21 01:08 cookiepool

The issue mainly affects Class Component.Function Component is normal!

I use Function components, and our app is broken when using lazy import 😅.

harrytran998 avatar Aug 02 '21 13:08 harrytran998

I'm using @loadable/component and Function components its worked fine for me.

guibwl avatar Aug 02 '21 16:08 guibwl

Hope your team can fix this soon! My team has the same trouble with this!

So do I!

SheldonWatson avatar Aug 03 '21 12:08 SheldonWatson

https://github.com/iheyunfei/vite-bug-report-4298 I create a reproduction repo. I both using create-react-app and vite to reproduce the problem successfully. So, I guess this is the problem of upstream react-refresh, not vite.

And the problem only happens on function component, Class Component seems to work.

hyf0 avatar Aug 14 '21 12:08 hyf0

damn. ~~It looks like if you give function component a name, then both c-r-a and vite example works.~~

     // Anonymous direct exports like export default function() {}
    // are currently ignored.

reference

import React from 'react'
const Foo = () => ( // works
  <div>foo</div>
)
export default Foo
import React from 'react'
export default () => ( // doesn't works
  <div>foo</div>
)

So, It's definitely not a bug of vite, but an intentional behavior of react facebook/react#21181.

hyf0 avatar Aug 14 '21 13:08 hyf0

I can't reproduce this either, working examples using lazy throughout the repo https://github.com/NazimHAli/imgur-explorer/blob/master/src/components/App.tsx#L5-L11

NazimHAli avatar Oct 16 '21 14:10 NazimHAli

import React from 'react'
const Foo = () => ( // works
  <div>foo</div>
)
export default Foo
import React from 'react'
export default () => ( // doesn't works
  <div>foo</div>
)

So, It's definitely not a bug of vite, but an intentional behavior of react facebook/react#21181.

I think using Foo works because component names have to start with a capital letter: https://reactjs.org/docs/components-and-props.html

Note: Always start component names with a capital letter. React treats components starting with lowercase letters as DOM tags. For example,

represents an HTML div tag, but <Welcome /> represents a component and requires Welcome to be in scope.

NazimHAli avatar Oct 16 '21 14:10 NazimHAli

The issue mainly affects Class Component.Function Component is normal!

I use Function components, and our app is broken when using lazy import 😅.

Have you solve the problem? How to do ?

nanslee avatar Oct 28 '21 02:10 nanslee

So, if I understood correctly, a named export works but a default export won't? Does wrapping a component with lazy forget about the naming? I have a setup like this and I'm having trouble with HMR:

// MyComponent.tsx
export const MyComponent: React.FunctionComponent

// App.tsx
const MyComponent = React.lazy(() => import('./components/MyComponent').then(
  res => ({ default: res.MyComponent })
)

const App = () => {
  return (
    <Route path='/' component={MyComponent} />
  )
}

Is there some other way I could try to get HMR working correctly?

RubenLaube-Pohto avatar Oct 28 '21 10:10 RubenLaube-Pohto

So, if I understood correctly, a named export works but a default export won't? Does wrapping a component with lazy forget about the naming? I have a setup like this and I'm having trouble with HMR:

Default exports work - working example:

https://github.com/NazimHAli/imgur-explorer/blob/master/src/components/App.tsx#L5-L13

const Explore = lazy(() => import("@/components/Explore"));
const Footer = lazy(() => import("@/components/Footer"));
const Header = lazy(() => import("@/components/Header"));
const ImageGrid = lazy(() => import("@/components/ImageGrid"));

NazimHAli avatar Oct 28 '21 12:10 NazimHAli

I see... Well, my setup is a bit more complex with routing, custom providers and hooks. In a nutshell, when HMR is triggered for a component in the lazy loaded path, a hook fails to get a parameter from the URL. It just returns a default value I've set without actually checking the URL again.

If I find the time, I'll try making a small reproduction.

RubenLaube-Pohto avatar Oct 29 '21 11:10 RubenLaube-Pohto

@RubenLaube-Pohto that would make it easier to help troubleshoot :+1:

NazimHAli avatar Oct 29 '21 13:10 NazimHAli

I'm encountering this issue and can't find any obvious places where i'm not defining a component as a const before export defaulting it.

Any additional input on how people got over this would be appreciated!

oller avatar Jul 06 '22 11:07 oller

I'm using @loadable/component and Function components its worked fine for me.

@guibwl After i used @loadable/component, it works fine。Thank

zqinmiao avatar Sep 05 '22 08:09 zqinmiao