vercel / next.js

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

Custom wrapper component does not receive actual MDX content in props.children when using app router #58943

Open LudoLogical opened 9 months ago

LudoLogical commented 9 months ago

Link to the code that reproduces this issue

https://github.com/LudoLogical/next-mdx-custom-wrapper-demo

To Reproduce

  1. npx create-next-app@latest -e reproduction-template

  2. Follow the steps from App Router > Building Your Application > Configuring > MDX in the docs:

    1. npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

    2. Create /mdx-components.tsx and add the following:

      import type { MDXComponents } from 'mdx/types'
      
      export function useMDXComponents(components: MDXComponents): MDXComponents {
        return {
          ...components,
        }
      }
    3. Change /next.config.js to read as follows:

      const withMDX = require('@next/mdx')()
      
      /** @type {import('next').NextConfig} */
      const nextConfig = {
        // Configure `pageExtensions` to include MDX files
        pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
        // Optionally, add any other Next.js config below
      }
      
      module.exports = withMDX(nextConfig)
    4. Create /app/test/page.mdx and add the following:

      # Welcome to my MDX page!
      
      This is some **bold** and _italics_ text.
      
      This is a list in markdown:
      
      - One
      - Two
      - Three
  3. Edit /mdx-components.tsx to add a custom component definition for wrapper:

    import { type ReactElement, type ReactNode } from "react";
    import type { MDXComponents } from "mdx/types";
    
    export function useMDXComponents(components: MDXComponents): MDXComponents {
     return {
       wrapper: ({ children }: { children: ReactNode }) => {
         console.log(children);
         console.log((children as ReactElement).props.children);
         return children;
       },
       ...components,
     };
    }
  4. npm run dev and navigate to localhost:3000/test

Current vs. Expected behavior

Current

The following is the result of the first console.log():

{
  '$$typeof': Symbol(react.element),
  type: [Function: _createMdxContent],
  key: null,
  ref: null,
  props: { params: {}, searchParams: {} },
  _owner: null,
  _store: {}
}

The second console.log() outputs undefined.

Expected

The behavior shown in "The X in MDX," a short talk by Rodrigo Pombo on the Vercel YouTube channel. Note that the talk concerns the pages router, but the equivalent behavior is highly desirable in app router so that the effect that Pombo creates can be replicated. The console output should be something like what Pombo shows here - i.e., it should be possible to manipulate the ReactElements generated from the input MDX to, for example, create a table of contents from the headers.

Verify canary release

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP Fri Jan 27 02:56:13 UTC 2023
Binaries:
  Node: 20.9.0
  npm: 10.1.0
  Yarn: 1.22.21
  pnpm: 8.10.5
Relevant Packages:
  next: 14.0.4-canary.17
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.1.3
Next.js Config:
  output: N/A

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

App Router, MDX (@next/mdx)

Additional context

Current workarounds:

  1. For some use cases, you can write custom remark and/or rehype plugins as detailed here
  2. You can wrap an imported <MyMDXContent /> ReactElement in a div and apply a callback ref in which you either manipulate the Elements directly or set a useState() variable whose contents are rendered if they exist with new ReactElement(s). (Of course, useEffect() with an empty dependency array can also be used to achieve this, but there can be some problems with that approach.)

Notably absent from this list is the use of a layout.tsx file, as console.log()ing the children prop there produces the same output shown in the "Current vs. Expected behavior" section above. The issue stems from the fact that the translation from MDX to React code has not yet taken place.

Possibly related reading (the potential fixes discussed within do not resolve this issue):

designbureau commented 8 months ago

+1

Edit by maintainers: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

designbureau commented 8 months ago

Seems to have changed between v14.0.3 and v14.0.4, it was working for me with v14.0.3.

SerafinDinges commented 6 months ago

Running into exactly the same issue. Following the steps from the tutorial and then trying to manipulate/read children in the wrapper returns no usable result.

Our use case is reading the content and creating a table of content for all heading elements.

Did you find any solution for this?

I can confirm it works in v14.0.3. We were using 14.2.0-canary.0 where it definitely doesn't work.

seangray-dev commented 5 months ago

Also not working in v14.1.3 after following docs

artola commented 2 months ago

Hi folks! Someone has a solution with the app router to get the TOC ?