DEV Community

CamelJohn
CamelJohn

Posted on

SVG Custom Componet

So, today I'm here to share about using SVG in React (with Vite).

For those of us who know, or don't know - svg are highly scalable (that's what the S stands for in SVG) vector graphics, and very useful.

they might feel intimidating, but once you get over yourself, they're quite fun, and you keep asking yourself how you ever did without them.

I love using javascript in conjunction with **typescript** helps the team develop a common language to work with (interfaces).

I feel that typescript serves a multitude of purposes, but, let's not forget it's not a real safety net - but more of a convenience. In the real world I'd advocate using something like joi or zod or other javascript type-checking libs.

so here's the stack:

  • vite
  • react
  • typescript
  • styled-components

there's a whole list, but we'll focus on these.

my use case is - wanting to create a generic (enough) SVG component, so that anyone trying to implement one - doe's not need to know what is available, but rather have autocompletion.

first, grab some svg's, maybe put them in your public folder.
make sure to scrub them down for any fill rules or strokes. makes the overrides easier.

styled-components is nice to have but you could go with scss or vanilla css - to grab nested svg elements.

here's the a breakdown of said component:

so, let's go over what we've done here:

first of all we're importing the SVG's as React components (keep in mind that there are other ways to harness the power of svg in react, as well as vite - that offers us a multitude of solutions I won't delve into in this post.

import { ReactComponent as Avatar } from '/public/avatar.svg';
import { ReactComponent as Dashboard } from '/public/dashboard.svg';
import { ReactComponent as Diamond } from '/public/dimond.svg';
import { ReactComponent as EmptyGames } from '/public/empty-games.svg';
import { ReactComponent as Logo } from '/public/is-logo.svg';
import { ReactComponent as Rocket } from '/public/rocket.svg';
import { ReactComponent as Text } from '/public/text.svg';
import { ReactComponent as NewWindow } from '/public/new-window.svg';
import { ReactComponent as PercentageCake } from '/public/percentage-cake.svg';
import { ReactComponent as NoData } from '/public/no-data.svg';
import { ReactComponent as Menu } from '/public/menu.svg';
import { ReactComponent as Breakdown } from '/public/breakdown.svg';
import { ReactComponent as Profile } from '/public/profile.svg';
import { ReactComponent as ClosedMenu } from '/public/menu-closed.svg';
import { ReactComponent as Collection } from '/public/apps.svg';
import { ReactComponent as Upload } from '/public/upload.svg';
import { ReactComponent as Empty } from '/public/empty.svg';
import { ReactComponent as Cart } from '/public/cart.svg';
import { ReactComponent as Collections } from '/public/collections.svg';
import { ReactComponent as Megaphone } from '/public/megaphone.svg';
import { ReactComponent as Wallet } from '/public/wallet.svg';
import { ReactComponent as Console } from '/public/console.svg';
import { ReactComponent as Back } from '/public/back.svg';
Enter fullscreen mode Exit fullscreen mode

next we're defining a component descriptor type - this is to help us for the SVG Record definition later, basically this is what the value of each key will be holding on that object.

type SVGComponentDescriptor = 
React.FunctionComponent<React.SVGProps<SVGSVGElement> 
& { title?: string | undefined; }>;
Enter fullscreen mode Exit fullscreen mode

we then proceed to map each key in the SVGKey type, this will be the actual autocompletion, so no real tricks here.

export type SVGKey =
    | 'Avatar'
    | 'Collection'
    | 'Dashboard'
    | 'Upload'
    | 'Diamond'
    | 'EmptyGames'
    | 'Logo'
    | 'Rocket'
    | 'Text'
    | 'NewWindow'
    | 'PercentageCake'
    | 'NoData'
    | 'Menu'
    | 'Breakdown'
    | 'Profile'
    | 'ClosedMenu'
    | 'Wallet'
    | 'Cart'
    | 'Collections'
    | 'Megaphone'
    | 'Console'
    | 'Back'
    | 'Empty';
Enter fullscreen mode Exit fullscreen mode

next, we'll define the props for the component, as you can see, it's a merge of the custom type prop, i've added, and react-typescript's own definition of what SVG props should look like on an svg element.

type SVGProps = { type: SVGKey; } & React.SVGProps<SVGSVGElement>;
Enter fullscreen mode Exit fullscreen mode

next up we have the SVG object, to be accessed by key-value pairs.

export const SVG: Record<string, SVGComponentDescriptor> = {
    Avatar,
    Collection,
    Dashboard,
    Upload,
    Diamond,
    EmptyGames,
    Logo,
    Rocket,
    Text,
    NewWindow,
    PercentageCake,
    NoData,
    Menu,
    Breakdown,
    Profile,
    ClosedMenu,
    Empty,
    Megaphone,
    Wallet,
    Cart,
    Collections,
    Console,
    Back,
};
Enter fullscreen mode Exit fullscreen mode

last, we have the exported component:

export default function FlameSVG(props: SVGProps) {
    const { type, ...otherProps } = props;
    const SVGComponent = SVG[type];
    return <SVGComponent {...otherProps} />;
Enter fullscreen mode Exit fullscreen mode

so, what we've done here is accept a merged set of props, made up from react implementation of the DOM svg props, and our own custom type.

we then "pluck" out the type prop, so that we can use it, as well as not propagate it to the DOM element which does not know this prop (react should throw an error if you do), and use the rest operator for the rest of the props.

this mean's we have full autocompletion for any svg props we'd like to use, as well as the type props.

we then access the SVG object Record we've created before, and it returns us a JSX SVG element (not really JSX, but I can't remember the proper definition at this moment).

that is what we return, in addition, we spread out the DOM props, that might have been used into the svg component. and voila ! auto completion, of both native props, as well as control over what svg will be returned in the DOM.

hope you found this useful, try It out, let me know what you think.

Top comments (0)