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

[docs] Using custom SvgIcon with SVGR does not work as described in the docs, with next.js App Router #39974

Open
1 task done
alexleach opened this issue Nov 23, 2023 · 6 comments
Assignees
Labels
component: SvgIcon The React component. nextjs support: docs-feedback Feedback from documentation page

Comments

@alexleach
Copy link

alexleach commented Nov 23, 2023

Duplicates

  • I have searched the existing issues

Related page

https://mui.com/material-ui/icons/#component-prop

Kind of issue

Broken demonstration

Issue description

Yesterday I started working with Material UI's Next.js App Router typescript example. First thing I tried doing was replacing the DashboardIcon with a custom svg logo.

I tried following the example code at https://mui.com/material-ui/icons/#component-prop, and immediately started getting issues:

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

Check the render method of MuiSvgIconRoot.

Lots of debugging and issue hunting later, I put a more complicated webpack config into next.config.js, as suggested in various Github issues, including (but not limited to) in the svgr and next.js repos:

module.exports = {
  webpack: (config, options) => {
    // Grab the existing rule that handles SVG imports
    const fileLoaderRule = config.module.rules.find((rule) =>
      rule.test?.test?.('.svg'),
    )
    config.module.rules.push(
      // Reapply the existing rule, but only for svg imports ending in ?url
      {
        ...fileLoaderRule,
        test: /\.svg$/i,
        resourceQuery: /\.svg\?url$/,
      },
      // Convert all other *.svg imports to React components
      {
        test: /\.svg$/i,
        issuer: fileLoaderRule.issuer,
        resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /\.svg\?url$/] },
        use: [{
          loader: '@svgr/webpack',
          options: {
            icon: 24,
          }
        }]
      },
    )
    // Modify the file loader rule to ignore *.svg, since we have it handled now.
    fileLoaderRule.exclude = /\.svg$/i
    return config
  },
 // [snip]

(My only addition here is to add the icon: 24 svgr/webpack option, otherwise my icon took up the full page)

However, using the <SvgIcon component={MyIcon} /> syntax in layout.tsx, I got another error:

Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
{$$typeof: ..., render: function}
^^^^^^^^

I managed to get around this, by restructuring my layout.tsx like so:

<SvgIcon>
  <MyIcon />
</SvgIcon>

I don't pretend to be a pro when it comes to next.js, webpack, and certainly not material-ui, which I'm only just starting to try and use, so I'm not suggesting this is the best solution or anything. But currently, the code examples in the docs don't work with the Next.js App Router, and it has taken me a full day's worth of time to come up with a working solution.

One other nice suggestion, by @pascalpp (gregberge/svgr#897 (comment)), is to add a types.ts file, which allows us to fallback to the default next.js webpack loader for svg files, by appending a string to the end of the URL:

 declare module '*.svg?url' {
  import { FC, SVGProps } from 'react'
  const content: FC<SVGProps<SVGElement>>
  export default content
}

However, this uses react's SVGProps, instead of material-ui's SvgIconProps or SvgIconOwnProps. I tried changing SVGProps for SvgIcon{Own,}Props, but it didn't work and I've given up on this for now.

Any feedback on this would be appreciated.

Context 🔦

I was just trying to replace the homepage icon with a custom svg logo. Followed code examples in the docs, but it didn't work...

@alexleach alexleach added status: waiting for maintainer These issues haven't been looked at yet by a maintainer support: docs-feedback Feedback from documentation page labels Nov 23, 2023
@mj12albert
Copy link
Member

@alexleach Could you share the <svg> that you are using?

@mj12albert mj12albert self-assigned this Nov 23, 2023
@alexleach
Copy link
Author

Sure, I've only tried the one to be fair (before and after resizing the canvas to 24 by 24px), as I didn't expect the svg to be the issue... This is the 24 x 24px version, which I resized in Inkscape yesterday.

<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright Bio IT Consulting Ltd 2023. All rights reserved. -->
<svg id="svg10" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<style id="style1" type="text/css">
  .st0{fill:none;stroke:#11999E;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}
  .st1{stroke-width:1.2}
  @media (prefers-color-scheme: light) { :root {filter:none;} }
  @media (prefers-color-scheme: dark) { :root {filter:contrast(0.8333333333333334) brightness(1.7);}.st0{stroke:#293533;} }
</style>

<g id="g10" transform="matrix(.40292 0 0 .40292 1.4187 .27503)"><g id="mag">
  <circle id="circle1" class="st0" cx="20.8" cy="17" r="16"/>
  <line id="line1" class="st0" x1="12" x2="17.1" y1="15.8" y2="22.8"/>
  <line id="line2" class="st0" x1="10.5" x2="13.1" y1="19.5" y2="22.9"/>
  <line id="line3" class="st0 st1" x1="31.1" x2="28.9" y1="33.5" y2="30.8"/>
  <line id="line4" class="st0 st1" x1="32.2" x2="34.3" y1="28.3" y2="31.1"/>
  <path id="path4" class="st0" d="m51.1 50.2-14.7-19c-0.5-0.7-1.4-0.8-2.1-0.3l-3.2 2.4c-0.7 0.5-0.8 1.4-0.3 2.1l14.7 19c1.2 1.5 3.4 1.8 4.9 0.6 1.6-1.1 1.9-3.3 0.7-4.8z"/>
</g><g id="petri">
  <circle id="circle4" class="st0" cx="15.2" cy="42.7" r="14.5"/>
  <path id="path5" class="st0 st1" d="m13.2 53-3.3-0.9c-0.8-0.2-1.3-1-1.1-1.8s1-1.3 1.8-1.1l3.3 0.9c0.8 0.2 1.3 1 1.1 1.8s-1 1.3-1.8 1.1z"/>
  <path id="path6" class="st0 st1" d="m20 47.5 1.8-3c0.4-0.7 1.3-0.9 2.1-0.5 0.7 0.4 0.9 1.3 0.5 2.1l-1.8 3c-0.4 0.7-1.3 0.9-2.1 0.5-0.6-0.5-0.9-1.4-0.5-2.1z"/>
  <path id="path7" class="st0 st1" d="m14.6 45.4-2.6-2.3c-0.6-0.6-0.7-1.5-0.1-2.1s1.5-0.7 2.1-0.1l2.6 2.3c0.6 0.6 0.7 1.5 0.1 2.1s-1.5 0.6-2.1 0.1z"/>
  <path id="path8" class="st0 st1" d="m5.6 37.6 2.3-2.6c0.6-0.6 1.5-0.7 2.1-0.1s0.7 1.5 0.1 2.1l-2.3 2.6c-0.6 0.6-1.5 0.7-2.1 0.1-0.6-0.5-0.6-1.5-0.1-2.1z"/>
  <circle id="circle8" class="st0 st1" cx="5.1" cy="44.1" r="1.5"/>
  <circle id="circle9" class="st0 st1" cx="15.8" cy="36" r="1.5"/>
  <circle id="circle10" class="st0 st1" cx="22.6" cy="38.5" r="1.5"/>
</g></g>
</svg>

N.B. This is a copyrighted image ©️ Bio IT Consulting Ltd

@mj12albert
Copy link
Member

@alexleach I cleaned up your svg even more (with Affinity Designer and svgo), in Next.js it will work using createSvgIcon in a Client component: https://codesandbox.io/p/devbox/nextjs-createsvgicon-p3fk6j?file=%2Fsrc%2Fcomponents%2FClientPage.tsx%3A14%2C10

(In my own experience I've found svgs with transform properties to be a nightmare)

As for getting it to work in a Server component, I haven't looked into whether it's an issue with the SVGR/webpack setup or with Material UI yet.

If I understand correctly, you got this to work? In a Server component?

<SvgIcon>
  <MyIcon />
</SvgIcon>

Would you mind sharing a reproduction? Probably make a minimal repository if you don't mind, it could be tricky trying to fiddle with Next.js config level stuff in CodeSandbox

@mj12albert mj12albert added component: SvgIcon The React component. and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Nov 23, 2023
@alexleach
Copy link
Author

Oh awesome, thanks for optimising that SVG even more :))

Yeah I'm hoping to apply even more transformations to it tbh, with one idea in particular to make a loading icon where the magnifying glass moves around and scales the Petri dish. Might be a while before I get around to that, but it's a plan anyway :))

Anyway, we digress. I'm out for the evening now (GMT+7 where I am). I'll take another look in the morning and will document/clone/provide you with a minimal, reproducible setup for this.

Thanks again for your speedy assistance, much appreciated 😊

@alexleach
Copy link
Author

Hi @mj12albert ,

Thanks again for this. I've not played much with CodeSandbox before, pretty cool how well it integrates with github and VSCode, but yeah, agreed it is a bit fiddly, slow and sketchy when playing with next.js, webpack, and coding in VSCode on wi-fi.

Anyway, I thought you made a great minimal example, so I've taken it from there, put it into my github, and have made a couple of commits / tags:

v0.1.0-alpha

- error Error: Unsupported Server Component type: {...}
at stringify (<anonymous>)

v0.2.0-beta

  • Installed @svgr/webpack
  • Configured it in next.config.js
  • Got a <SvgIcon> component working inside of layout.tsx, by nesting <MySvgIcon> inside of it

I was having a think about the error when using the <SvgIcon component={MySvgIcon} /> coding style, where it complains as follows:

Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
<... component={function} sx={{...}}>
^^^^^^^^^^

This seems to imply that <SvgIcon> is a Client component. I kind of consider svg files as static. This is why I save them as files in my /public/assets folder. Therefore, shouldn't they be Server components? Or am I thinking about this wrongly?

Thanks again for your prompt help looking into this.
BW,
Alex

@pranav-growthx
Copy link

I was working on something similar. Almost all of MUI components including SvgIcon are client components.

In the App router, you can pass server component as a child to a Child component. Probably thats why it does not throw you any error

For svg icons, I am calling them server components cause if you havent used "use client" in your component, Next will consider it as a server component

https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: SvgIcon The React component. nextjs support: docs-feedback Feedback from documentation page
Projects
None yet
Development

No branches or pull requests

3 participants