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

_owner field of React component set to different value after enabling turbopack #71878

Closed
nphmuller opened this issue Oct 25, 2024 · 6 comments
Closed
Labels
bug Issue was opened via the bug report template. linear: turbopack Confirmed issue that is tracked by the Turbopack team. Turbopack Related to Turbopack with Next.js.

Comments

@nphmuller
Copy link
Contributor

nphmuller commented Oct 25, 2024

Link to the code that reproduces this issue

https://github.com/nphmuller/next15-turbo-maxstack-repro

To Reproduce

  1. Clone repo
  2. npm install && npm run dev
  3. Open localhost:3000
  4. The page renders properly, but in the console you see RangeError: Maximum call stack size exceeded.

If you remove --turbo in package.json there is no error.

Main part of repro: https://github.com/nphmuller/next15-turbo-maxstack-repro/blob/master/app/wrapper.tsx

Manual repro:

Based on create-next-app.

  1. Create wrapper.tsx:
"use client";

const Component = () => <div>Stub component</div>;

// This line runs fine without turbopack and causes an infinite loop with turbopack
const _cloned = deepClone(<Component />);

// If we don't render anything and just import for the side effect, the error doesn't happen
// For the error to happen we need to import this wrapper and render it somewhere
export default function Wrapper({ children }: { children: React.ReactNode }) {
  return <>{children}</>;
}

// ----- the code below is taken from the MUI source and it's how they implemented deepClone -----
// see: https://github.com/mui/material-ui/blob/2359c0badfb5a8d746ccc83c1d082c4ad70a542b/packages/mui-utils/src/deepmerge/deepmerge.ts

function deepClone<T>(source: T): T {
  if (!isPlainObject(source)) {
    return source;
  }

  const output: Record<any, any> = {};

  Object.keys(source).forEach((key) => {
    if (key === "_owner") {
      // !!!!! Here's the difference between webpack and turbopack !!!!!
      // !!!!! In webpack the value of _owner is null !!!!!
      // !!!!! In turbopack the value of _owner is not null !!!!
      console.log("key " + key + " is null " + source[key] === null);
      console.log(source[key]);
    }
    output[key] = deepClone(source[key]);
  });

  return output;
}

// https://github.com/sindresorhus/is-plain-obj/blob/main/index.js
function isPlainObject(item: unknown): item is Record<keyof any, unknown> {
  if (typeof item !== "object" || item === null) {
    return false;
  }

  const prototype = Object.getPrototypeOf(item);

  const res =
    (prototype === null ||
      prototype === Object.prototype ||
      Object.getPrototypeOf(prototype) === null) &&
    !(Symbol.toStringTag in item) &&
    !(Symbol.iterator in item);

  return res;
}
  1. Use wrapper.tsx in either layout.tsx or page.tsx (doesn't matter which one):

layout.tsx:

import Wrapper from "./wrapper";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <Wrapper>{children}</Wrapper>
      </body>
    </html>
  );
}

Current vs. Expected behavior

Expected: No error

Current:
Error in console. Full stack trace:

⨯ RangeError: Maximum call stack size exceeded
    at isPlainObject (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:376:23)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:384:10)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)

Provide environment information

❯ npx next info

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.0.0: Mon Aug 12 20:51:54 PDT 2024; root:xnu-11215.1.10~2/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 20.13.0
  npm: 10.8.3
  Yarn: N/A
  pnpm: 9.6.0
Relevant Packages:
  next: 15.0.1 // Latest available version is detected (15.0.1).
  eslint-config-next: 15.0.1
  react: 19.0.0-rc-69d4b800-20241021
  react-dom: 19.0.0-rc-69d4b800-20241021
  typescript: 5.6.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Turbopack

Which stage(s) are affected? (Select all that apply)

next dev (local)

Additional context

I also tested against the latest preview release (15.0.2-canary.7) and the issue still repros.

It seems that the value of the _owner key of the to be cloned component has a different value between webpack (null) and turbopack (see below). The value in turbopack seems to cause an infinite loop in the deepClone code.

The value of _owner when ran via turbopack is:

{
  parent: {
    parent: { parent: [Object], type: 'html', owner: [Object], stack: null },
    type: 'body',
    owner: {
      name: 'RootLayout',
      env: 'Server',
      key: null,
      owner: null,
      props: [Object]
    },
    stack: null
  },
  type: {
    '$$typeof': Symbol(react.lazy),
    _payload: Promise {
      status: 'resolved_module',
      value: [Array],
      reason: null,
      _response: [ResponseInstance],
      _debugInfo: []
    },
    _init: [Function: readChunk],
    _debugInfo: []
  },
  owner: {
    name: 'RootLayout',
    env: 'Server',
    key: null,
    owner: null,
    props: { children: [Object], params: [Getter] }
  },
  stack: null
}

While the repro looks pretty exotic, it's mainly there to have an example without any external dependencies.

Here's a more practical example of the code that originally caused the error: https://github.com/nphmuller/next15-turbo-maxstack-repro/blob/0d1bfdfbc86441fc0603c151e36ecfac4892ace9/app/wrapper.tsx

@nphmuller nphmuller added the bug Issue was opened via the bug report template. label Oct 25, 2024
@github-actions github-actions bot added the Turbopack Related to Turbopack with Next.js. label Oct 25, 2024
@gruckion
Copy link

Can you apply the binary search approach to debugging. Take your root page / layout and make them just show basic “hello” and “world”. The issue should go away.

then reintroduce the code bit by bit. So introduce your root layout but keep the page as just “world”. This will allow you to determine if the issue exists in the layout or in the page / the nested pages.

Slowly uncomment parts of your code bit by bit until the issue comes back. Then you can identify what erroneous code is causing the issue.

Turbopack works fine for me. But when I first tried to set it up I had to do the above approach to figure out what was causing me issues.

I hope this advice helps. It can be applied to many problems.

https://www.codewithjason.com/binary-search-debugging/

@nphmuller
Copy link
Contributor Author

nphmuller commented Oct 25, 2024

You’re totally right and I should have explained this better. I tried to follow this approach when making the repro, and the part that is causing the error is adding <InfoSharp /> (or any other icon from the library) at that specific place in createTheme(). When I render the icon directly in my component, instead of referencing it in createTheme(), the error doesn't happen.

When I’m at my pc again I’ll edit my post and look further. But I should have at least mentioned this before in my repro…

I’ll also try to repro this issue in plain code without using MUI, but I haven’t be able to so far.

@nphmuller
Copy link
Contributor Author

Rewrote the repro from scratch, without any dependencies. Let me know if I can do more to clarify! :)

@timneutkens timneutkens added the linear: turbopack Confirmed issue that is tracked by the Turbopack team. label Oct 29, 2024
@nphmuller
Copy link
Contributor Author

nphmuller commented Oct 29, 2024

Issue still occurs on just 15.0.3-canary.1.

@nphmuller
Copy link
Contributor Author

nphmuller commented Oct 30, 2024

After upgrading from 15.0.1 to 15.0.2 this issue also occurs without turbopack when running next dev.

After investigating 15.0.2-canary.9 still works fine (on non turbo dev) and on 15.0.2-canary.10 the issue starts happening.

Most likely it was this PR (because it aligned turbopack behaviour with webpack): #71968

I'm now wondering if this should be considered a bug or not. You can make the point that the deepClone behaviour is incorrect, but this code still works fine on a regular old client-only app with (for example) Vite.

Since I think it might be valid that it's a MUI issue, I've created this issue over there: mui/material-ui#44278

A possible reason why this might be a Next (or even a React?) issue, is that I can't see why _owner now suddenly has a value instead of null. The component shouldn't have an owner, because it is rendered independently without a parent component.

@nphmuller nphmuller changed the title RangeError: Maximum call stack size exceeded after enabling turbopack in dev _owner field of React component set to a different value after enabling turbopack Oct 30, 2024
@nphmuller nphmuller changed the title _owner field of React component set to a different value after enabling turbopack _owner field of React component set to different value after enabling turbopack Oct 30, 2024
@nphmuller
Copy link
Contributor Author

Closing this, because the issue has been resolved for MUI, and because the scope changed since creating this issue. The customer changed from "Why is this different between Webpack and Turbopack?" to "Why is this field suddenly set?".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template. linear: turbopack Confirmed issue that is tracked by the Turbopack team. Turbopack Related to Turbopack with Next.js.
Projects
None yet
Development

No branches or pull requests

3 participants