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

How to dynamically introduce it after version 4.0 #90

Open
qiaogaolong opened this issue Sep 20, 2023 · 18 comments
Open

How to dynamically introduce it after version 4.0 #90

qiaogaolong opened this issue Sep 20, 2023 · 18 comments

Comments

@qiaogaolong
Copy link

How to dynamically introduce it after version 4.0?

@alexbaulch
Copy link

alexbaulch commented Oct 4, 2023

I am also getting unexpected behaviour when trying to dynamically import. The import returns a path string instead of a React component.

I was playing with a stack blitz from this article that was demoing how to work with vite/svgr and dynamic imports. It references an outdated version of this plugin so I forked the dynamic import stack blitz and updated all the packages. As you can see in this minimal repro the plugin appears to be returning a path string to the component instead of a React component which causes React to blow up.

Stack blitz

@brazillierjo
Copy link

brazillierjo commented Oct 10, 2023

I'm also having errors since version 4. My test with vitest returns this error on all my tests that contains a component SVG :

'Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s%s'

No problem on version 3.

@SetupCoding
Copy link

Same issue here. Dynamic imports do not work as described by @alexbaulch

@pd4d10
Copy link
Owner

pd4d10 commented Oct 25, 2023

I am also getting unexpected behaviour when trying to dynamically import. The import returns a path string instead of a React component.

It seems caused by the iconName variable, which can't be determined at compile time.

Is this kind of usage working at v3.0?

@LiamPham98
Copy link

I fixed the above problem by:

vite.config.ts
image

useDynamicSvg.ts
image

I hope my way will help you!!!

@xsjcTony
Copy link

xsjcTony commented Nov 21, 2023

@pd4d10 Yes, it's working before v4. The v4 change breaks the dynamic import. The imported value is the path handled by vite instead of the React component by this plugin, even ?react is added at the end of the string.

Regarding @trungpham71198 's solution, I can't take it since I sometimes still use the normal .svg import to avoid the React component wrapper by this plugin.

This critical is preventing me from upgrading to Vite 5, since it's only supported in v4.2.0 of this plugin.

Please have a look into it, cheers. 💚

@eMerzh
Copy link

eMerzh commented Nov 22, 2023

Just to add my 2 cents it' no solutions seems to work in vitev5
and it's outputing error about missing loader

like here https://stackblitz.com/edit/vitejs-vite-fz5lgu?file=src%2FSvgIcon.tsx,package.json

@publicJorn
Copy link

publicJorn commented Nov 29, 2023

Still a work-around, but if you still want to import normal svg's you can change the file name of svg's you do want to be picked up by svgr and change the include option accordingly:

  • icons/name.svgr.svg (added .svgr to the name)

In vite config:

svgr({
  include: '**/*.svgr.svg'
})

Then your import should also append .svgr so, something like:

await import(`path/to/${iconName}.svgr.svg`)

@xsjcTony
Copy link

xsjcTony commented Dec 19, 2023

Any official fix on this please? @pd4d10

@xsjcTony
Copy link

This issue is preventing me from upgrading to Vite5 😭

@arielmints
Copy link

Same issue for me, can't dynamically import SVGs after upgrading to version 4 :(

@qinhua
Copy link

qinhua commented Jan 6, 2024

我通过以下方式解决了上述问题:

vite.config.ts 图像

useDynamicSvg.ts 图像

希望我的方法对你有帮助!!!

Invalid in versions after 3.3.0

@xsjcTony
Copy link

xsjcTony commented Feb 8, 2024

Any updates?

@VikomiC
Copy link

VikomiC commented Feb 26, 2024

@trungpham71198

I hope my way will help you!!!

Thanks for your advice. For me it worked partially.
I have dynamic SVGs and usual SVGs.
Issue was solved with usual SVGs when import with "?react". But it was not worked for dynamic.
Then I found, that after some configuration changes dynamic imports started to work, but usual SVG started to show errors about props.

So, I went to vite.config.ts and added 2 types of imports:

export default defineConfig({
  ...
  plugins: [
    ...
    // svgr options: https://react-svgr.com/docs/options/
    svgr({
      svgrOptions: { icon: true },
      include: ['**/*.svg', '**/*.svg?react'],
      exclude: [],
    }),
    ...
  ],
  ...
});

This works for me.

@TPLCarloTaleon
Copy link

TPLCarloTaleon commented Sep 12, 2024

None of the solutions are working for me.

But in any case (unrelated solution, but), as an alternative approach. what's stopping you from just defining:

// icons/index.ts
export { default as IconUp } from "./up.svg?react";
export { default as IconDown } from "./down.svg?react";
export { default as IconLeft } from "./left.svg?react";
export { default as IconRight } from "./right.svg?react";

And then just defining a map:

import type { ReactNode } from "react";
import { IconUp, IconDown, IconLeft, IconRight } from "./icons";

const dynamicIcons: Record<string,  ReactNode> = {
   "up": <IconUp />,
   "down": <IconDown />,
   "left": <IconLeft />,
   "right": <IconRight />,
} 


// And then using it in your jsx like:

let myDynamicValue = "up" | "down" | "left" | "right";

{dynamicIcons[myDynamicValue]}

@VikomiC
Copy link

VikomiC commented Sep 12, 2024

what's stopping you from just defining:

You have defined list of icons.
In my case I'm using a library with tons of icons (as an example, currencies icons).
My app can have any currency in the list. I want to get them dynamically, not to list all of them in the imports.

Simple imports works. But dynamic - not :(.

@TPLCarloTaleon
Copy link

WAIT! I stand corrected! #90 (comment) - This indeed works lol.

Also @VikomiC. The reason why static imports import TestIcon from "@/components/icons/test.svg" might not work for you anymore is because you should actually copy the contents of vite-plugin-svgr/client and override the regular .svg imports as well from string to ReactComponent like so:

// icon.d.ts (in your root folder)

/**
 * So import .svg becomes a ReactComponent instead of a string. Enabled by `vite-plugin-svgr`.
 */
declare module "*.svg" {
  import * as React from "react";

  const ReactComponent: React.FunctionComponent<
    React.ComponentProps<"svg"> & { title?: string }
  >;

  export default ReactComponent;
}

✅ Anyway, here's the whole code that works:

// icon.tsx
type IconSVG =
  | "caret-down"
  | "chart"
  | "copy"; // extend as you wish.

export type IconProps = {
  src: IconSVG;
} & React.SVGProps<SVGSVGElement>;

export const Icon = ({ src, ...svgProps }: IconProps) => {
  const {loading, SvgIcon } = useDynamicSvgImport(src);

  if (loading) {
    return null;
  }

  if (!SvgIcon) return null;

  return <SvgIcon width="1em" height="1em" {...svgProps} />
};

/** Reference: https://stackblitz.com/edit/vitejs-vite-fz5lgu?file=src%2FSvgIcon.tsx,package.json */
export function useDynamicSvgImport(iconName: string) {
  const importedIconRef = useRef<React.FC<React.SVGProps<SVGElement>>>();
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<unknown>();

  useEffect(() => {
    setLoading(true);
    // dynamically import the mentioned svg icon name in props
    const importSvgIcon = async (): Promise<void> => {
      // please make sure all your svg icons are placed in the same directory
      // if we want that part to be configurable then instead of iconName we will send iconPath as prop
      try {
        importedIconRef.current = (
          await import(`@/assets/icons/${iconName}.svg?react`)
        ).default; // svgr provides ReactComponent for given svg path
      } catch (err) {
        setError(err);
        console.error(err);
      } finally {
        setLoading(false);
      }
    };

    importSvgIcon();
  }, [iconName]);

  return { error, loading, SvgIcon: importedIconRef.current };
}
// vite.config.ts
export default defineConfig({
  ...
  plugins: [
    svgr({
      svgrOptions: { icon: true },
      include: ['**/*.svg', '**/*.svg?react'],
      exclude: [],
    }),
  ],
  ...
});

@alundiak
Copy link

alundiak commented Oct 26, 2024

Maybe a bit different, because I don't want to sue custom hook (as suggested above useDynamicSvgImport), I also don't need to render icon by string file name. I need SIMPLY render all SVG icons on one page:

When I use SIMPLE IMPORT I have all good:

const iconModules = import.meta.glob('./*.svg', { eager: true });

export const IconsPage = () => {
  return (
    <div>
      {Object.entries(iconModules).map(([path, module], index) => {
        // @ts-ignore
        const IconComponent = module.default;
        return (
          <div key={index} style={{ display: 'inline-block', margin: '10px' }}>
            <a href={`src/icons/${path}`}>
              <img src={IconComponent} width="30px"></img>
            </a>
          </div>
        );
      })}
    </div>
  );
};

But when I want to rely on svgr plugin I do code this way:

const iconModules = import.meta.glob('./*.svg?react', { eager: true }); // CHANGED

export const IconsPage = () => {
  return (
    <div>
      {Object.entries(iconModules).map(([path, module], index) => {
        // @ts-ignore
        const IconComponent = module.default;
        return (
          <div key={index} style={{ display: 'inline-block', margin: '10px' }}>
            <a href={`src/icons/${path}`}>
              <IconComponent width="30px" />  // CHANGED
            </a>
          </div>
        );
      })}
    </div>
  );
};

Problem is that iconModules is empty {} and now my question:

SHOULD I be able to dynamically render SVG icons this way? Have I missing something?

PS1. When I include single SVG icon file all is ALSO GOOD:

import ErrorIcon from '@icons/error_icon.svg?react';
import DeleteIcon from '@icons/delete.svg?react';

PS2. I also tried importing this way:

const iconModules = await import('./*.svg').then(module => module.default);

But then Vite Build fails at all. Yes, I assumed import can return me array of modules :)

PS3.
In vite.config.ts => only svgr() (I tried to add include but didn't help).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests