vite-plugin-solid-svg icon indicating copy to clipboard operation
vite-plugin-solid-svg copied to clipboard

How to dynamically import svg file?

Open geohuz opened this issue 1 year ago • 4 comments
trafficstars

I'm trying to do dynamic import svg file based on user select, such as:

import { onMount } from "solid-js";

export const DynaSvg = (props) => {

  let SvgComp;
  onMount(async () => {
    SvgComp = await import(`./${props.name}.svg`);
    console.log(SvgComp);
    // SvgComp.ref = props.parentRef;
  });

 return (
  <Show when={SvgComp}>
    <div>
      <SvgComp />
    </div>
  </Show>
)
};

But it doesn't work, is there anyway to do this?

geohuz avatar Mar 04 '24 15:03 geohuz

The code below works but I'm not sure if it is the best practise to do it:

import { onMount, createSignal, Show } from "solid-js";

export const DynaSvg = (props) => {
  const [loaded, setLoaded] = createSignal(false);
  let SvgComp;

  onMount(async () => {
    SvgComp = (await import(`./${props.name}.svg`)).default;
    setLoaded(true);
  });

  return (
    <>
      <Show when={loaded()}>
        <SvgComp />
      </Show>
    </>
  );
};

geohuz avatar Mar 04 '24 16:03 geohuz

I don't know what the best practices are in this case, but Solid.js has lazy for loading an asynchronous component and Dynamic for switching between components. Depending on your needs, you can use both:

For example:

import { render, Dynamic } from 'solid-js/web'
import { lazy, createSignal, createMemo } from 'solid-js'

const [iconName, setIconName] = createSignal('');

const SelectedIcon = createMemo(() => {
  if (!iconName()) {
    return <></>
  }
  return lazy(() => import(`./svgs/${iconName()}.svg?component-solid`))
})


function App() {
  return (
    <div>
        <button onClick={() => setIconName('rect')}>rect</button>
        <button onClick={() => setIconName('circle')}>circle</button>
        <p>loaded Icon: <Dynamic component={SelectedIcon()} /></p>
    </div>
  )
}
render(() => <App />, document.getElementById('root') as HTMLElement)

jfgodoy avatar Mar 08 '24 16:03 jfgodoy

I was only able to get this working correctly with this approach. Vite will handle dynamic imports so we can remove lazy which simplifies things... After that we need to use createResource to create a reactive scope for importing the file. Then, instead of using Suspense, Show can be used to wait for the resource to be ready.

export function Icon(props: IconProps): JSX.Element {
  const [lazyIcon] = createResource(
    () => props.icon,
    async (iconName) => {
      try {
        const lazyFile = await import(
          `./icons/icon-${iconName}.svg?component-solid`
        );
        return lazyFile.default;
      } catch {
        return null;
      }
    }
  );

  return (
    <Show when={lazyIcon()} fallback={<div>A fallback here</div>}>
      {(LoadedIcon) => (
        <div>
          {LoadedIcon()}
        </div>
      )}
    </Show>
  );
}

Vite automatically creates a chunk for each possible dynamic import in the pattern, so for me this is a partial screencap of what happens when I build: image

onx2 avatar Aug 03 '24 05:08 onx2