vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.41k stars 26.91k forks source link

Dynamic import named component Typescript error | Dynamic export using Promise resolve results in warning "Did not expect server HTML to contain a ..." #33848

Closed b-novikov-ipersonality closed 2 years ago

b-novikov-ipersonality commented 2 years ago

Run next info (available from version 12.0.8 and up)

Operating System:
      Platform: linux
      Arch: x64
      Version: #1 SMP PREEMPT Thu, 27 Jan 2022 14:18:25 +0000
Binaries:
      Node: 16.13.2
      npm: 8.4.0
      Yarn: 1.22.17
      pnpm: N/A
Relevant packages:
      next: 12.0.8
      react: 17.0.2
      react-dom: 17.0.2

What version of Next.js are you using?

12.0.8

What version of Node.js are you using?

16.13.2

What browser are you using?

Google Chrome Version 99.0.4844.11 (Official Build) dev (64-bit)

What operating system are you using?

Linux (Arch)

How are you deploying your application?

next start | digital ocean

Describe the Bug

1) Typescript error when dynamically import named component (see To Reproduce section for codesandbox and instructions) 2) When exporting component as a dynamic module

const Component = () => {
  return (
    ...
  );
};
export default dynamic(() => Promise.resolve(Component), { ssr: true }); 

you get Did not expect server HTML to contain a <section> in <div> warning in console if and only if ssr property is set to true. 3) When dynamically imported component is used with dynamically exported component in the same component, another warning occurs: Text content did not match. Server: "Hero header" Client: "New Hero header"

Expected Behavior

1) When dynamically importing named component, type checking does not result in error 2) Component dynamically exported with ssr set to true does not give a warning 3) Don't know if its legal to use dynamic import with dynamically exported component in one place. If it is, it should give a warning.

To Reproduce

https://codesandbox.io/s/beautiful-proskuriakova-s1lii?file=/pages/index.tsx

1) Open about.tsx file in pages folder to see typescript error. 2) Open index.tsx file and corresponding page in browser, open Console you will see a warning: Did not expect server HTML to contain a <section> in <div>. 3) Open furthermore.tsx file and corresponding page in browser, in Console you will see warning Text content did not match. Server: "Hero header" Client: "New Hero header"

Steps to reproduce problem №1: 1) Create a component that uses named export:

export const Component = () => { ... };

2) Using dynamic, import that component:

const Component = dynamic(() => import("../components/Component").then(module => module.Component);

3) You will see Typescript doesn't like Component.

Steps to reproduce problem №2: 1) Create a component:

const Component = () => { ... };

2) Export it using dynamic:

export default dynamic(() => Promise.resolve(Component), { ssr: true }); // `srr` MUST BE SET TO `true` TO OCCUR!

3) Use the Component somewhere:

import Component from "../Component";

const AnotherComponent = () => {
  return (
    <Component />
  )
};

4) Open AnotherComponent in a browser, open the console, see the error.

Steps to reproduce problem №3: 1) Use component from problem №1 somewhere in AnotherComponent from problem №2 - basically you need to have component imported dynamically dynamic(() => import(...)) and component exported dynamically for error to occur 2) See warning in console

atarek12 commented 2 years ago

Open about.tsx file in pages folder to see typescript error.

You are exporting the <NewHero/> component as default, so you don't need the .then block when you are importing it.

const Component = dynamic(() => import("../components/Component").then(module => module.Component); // wrong
const Component = dynamic(() => import("../components/Component") // correct

Only use the .then module, when you are using named export from the file you are importing from.

// NewHero.tsx
function NewHero(){return ....}
export NewHero
.
.
// About.tsx
const Component = dynamic(() => import("../components/Component").then(module => module.NewHero);

for further reading: NextJs Dynamic Import

Open index.tsx file and corresponding page in browser, open Console you will see a warning: Did not expect server HTML to contain a <section> in <div>.

Open furthermore.tsx file and corresponding page in browser, in Console you will see warning Text content did not match. Server: "Hero header" Client: "New Hero header"

These two warnings will be gone when you export the <Hero/> component in a normal way.

export default Hero

I didn't find a solid reference for the usage of the export default dynamic() method, but personally, I am using it only with nextjs pages whenever I need this page to not render in the server-side

export default dynamic(() => Promise.resolve(CartPage), { ssr: false });

This could be helpful if you are using the browser local storage to store the cart times, or when you are doing client-side authentication and need to prevent the app to render the UserProfile page, and so on..

But when talking about code splitting and lazy loading a component with nextjs, I always use dynamic import

By the way, NextJs present a new API to handle client and server component based on React18 but it is still on beta, you can read more from here

b-novikov-ipersonality commented 2 years ago

@atarek12 please read the issue carefully and check codesandbox I provided. NewHero component is a named export.

atarek12 commented 2 years ago

@b-novikov-ipersonality, you are misunderstanding the difference between named and default exports.

For further reading: MDN

b-novikov-ipersonality commented 2 years ago

@atarek12 Mr. Senior Engineer, could you please carefully read my issue and carefully inspect code I provided? You should try to reproduce problem first before leaving misleading comments.

edit: to make things clear, I removed default export from NewHero component, leaving only named export

atarek12 commented 2 years ago

I don't why you are talking to me like this, I just needed to help, by the way, I did not see type errors now after your updates.

b-novikov-ipersonality commented 2 years ago

@atarek12

the error I'm talking about ![error image](https://i.imgur.com/h5hLL7I.png) ![export component](https://i.imgur.com/ULYGGeE.png)
atarek12 commented 2 years ago

@b-novikov-ipersonality it is not visible on my side

image

b-novikov-ipersonality commented 2 years ago

@atarek12 you need to be logged in to see the error. It might be a better idea to reproduce it locally.

atarek12 commented 2 years ago

@b-novikov-ipersonality now I can see it, I have figured out that you need to pass the component props interface manually into the dynamic component. The typescript should detect it by itself, but it has trouble when using Promise.then()

const NewHero = dynamic<NewHeroProps>(() =>
  import("../components/NewHero").then((mod) => mod.NewHero)
);

check this my forked code sandbox

b-novikov-ipersonality commented 2 years ago

Makes sense. Thank you for looking into it, @atarek12 Closing the issue because other mentioned problems are related to undocumented feature.

balazsorban44 commented 2 years ago

@b-novikov-ipersonality I kindly ask you to refrain from comments like "Mr. Senior Engineer," in the future, as it might sound disrespectful towards @atarek12 who tried to help you. Please have a look at our Code of Conduct.

I'm happy to see that the problem sorted itself.

github-actions[bot] commented 2 years ago

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.