Templarian / MaterialDesign-React

Dist for Material Design Icons React Component for JS/TypeScript
https://materialdesignicons.com
MIT License
138 stars 20 forks source link

How do you dynamically load an Icon? #34

Open ElixirMike opened 4 years ago

ElixirMike commented 4 years ago

So, in my app, I want to provide an Icon picker...let the user choose their icon. Once saved, I would use it on other pages in the app.

How do I dynamically import this specific icon on those pages?

Templarian commented 4 years ago

Yeah, we need to document this process and how it should work. It unfortunately requires a server-side component if one wants to use all 5k icons or it can become a performance issue for pages with lots of icons.

Do you want to provide all ~5k icons in the icon picker or a subset? We/community need to make some demos of both examples.

ElixirMike commented 4 years ago

I was just looking to create a subset...we've picked out about 800 icons to use. For now, what I did was create a custom object with array of icons, including a tag, name of icon and path.

Like this where I insert the 800 icons. var myicons = [{tag:'phones', name='mdiphone', path: "....full svg path", {}, {}, etc ]

It's lazy loaded on icon picker page..but when a user selects the icon, I simply store the svg path in db/cache.

Then, in the rest of my application, I can simply use the svgpath and display, so it's very fast.

It would be super helpeful to have someway of picking icons you want and then generating this type of object which can be used to display them on icon selection page.

AmirHosseinKarimi commented 4 years ago

I am trying to use dynamic import from this article But in this article only can import whole file, refer to @mdi/js/mdi.js all icons are stored to this single file. Is there anyway to dynamically import specific icon from this file?

Templarian commented 4 years ago

@AmirHosseinKarimi You could use the @mdi/svg package and fetch the individual SVG's by their icon name in a wrapper <LazyIcon name={iconName}>. Since <Icon> takes any path data, you could regex for the path data out of the SVG request and put it into <Icon>.

const data = body.match(/d="([^"]+)"/)[1];

Obviously write a light cache layer into your component so you don't make unnecessary requests for the path data.

Templarian commented 4 years ago

The @mdi/js package is only really aimed at compile time for tree-shaking. The @mdi/svg also contains the meta.json if you need to generate a list of icons (recommend to trim down this file as it could be heavy for use on the web).

AmirHosseinKarimi commented 4 years ago

Sorry I did not understand

Templarian commented 4 years ago

@AmirHosseinKarimi https://github.com/Templarian/MaterialDesign-SVG contains individual SVG files you can deploy. These could be requested by their name in a wrapping component. You only need the SVG path data aka <path d="this content"/> so that regex will get that (made a typo and edited it above with the correct regex).

AmirHosseinKarimi commented 4 years ago

So I have created a package (materialdesign-js) to dynamically load an icon. The description is clear about how this package working. This package is a dependency of another package, which I will create for React that automatically load the icon.

Currently if you need a dynamically load icon, you can create the following component:

import React from "react";
import { Icon as MDIcon } from "@mdi/react";

class Icon extends React.Component {
  render() {
    let icon = require(`materialdesign-js/icons/${this.props.icon}`).default;
    if (!icon) {
      throw Error(`Could not find materialdesign-js/icons/${icon}`);
    }
    return <MDIcon path={icon} />;
  }
}

export default Icon;

and use like this:

import Icon from "./Icon";
...
<Icon icon="mdiAccount" />

If this package is helped you please give a star: GitHub stars

Also if you like to contribute, just look at the MaterialDesign-JS repository.

Templarian commented 4 years ago

We recommend using a peer dependency for @mdi/js. Referencing a specific version usually doesn't work out in the long run. Learn more:

Yours will be more lightweight than the @mdi/svg approach, but similar idea loading individual glyphs.

We will work to open source the IndexedDB solution as it will be more ideal performance wise. Also makes it easier in cases where one may need to search all icons in a list.

AmirHosseinKarimi commented 4 years ago

Good, I create this package because I need dynamic load icons in my current project. So always there is an better idea. I am waiting for IndexedDB solution to try it.

AmirHosseinKarimi commented 4 years ago

Finally I have find a way which can use dynamic load MDI and also do not decrease the performance of app in development and runtime. Just look at this gist: https://gist.github.com/AmirHosseinKarimi/801931665a0845ccc25ed486431abf72

craigrileyuk commented 2 years ago

https://gist.github.com/AmirHosseinKarimi/801931665a0845ccc25ed486431abf72

You're requiring the entire @mdi/js library here, so any development or production build of the app would have to include the whole package (around 2.3MB).

AmirHosseinKarimi commented 2 years ago

@craigrileyuk Yea you're right. It's a little bit old. But I think the dynamic load example in the MaterialDesign-JS repository readme is correct and working.

ironAngel2000 commented 4 months ago

maybe a little late but this works for me fine:

import React, { useEffect, useState } from "react";
import { Icon as MDIcon } from "@mdi/react";

type props = {
    icon: string,
    size: number,
}

const LazyIcon = (props: props) => {

    const [iconPath, setIconPath] = useState<string>('');

    useEffect(() => {
        import('@mdi/js').then((icons:any) => {
            if (icons[props.icon]) {
                const icon = icons[props.icon];
                setIconPath(icon.toString());
            }
        });
    }, [props.icon]);

    return (<>{iconPath !== '' ?
        <MDIcon path={iconPath} size={props.size}></MDIcon>
        : null
    }</>);

}

export { LazyIcon };