Home > other >  How should I properly type SVG markup inside svg tags (React NextJS)
How should I properly type SVG markup inside svg tags (React NextJS)

Time:07-11

I have a Svg component like so

interface SvgIconProps {
  children: React.ReactNode;
  strokeWidth?: number;
  width?: number;
  height?: number;
  className?: string;
}

export const SvgIcon = ({
  children,
  strokeWidth = 1,
  width = 24,
  height = 24,
  className,
}: SvgIconProps) => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      className={className}
      fill="none"
      viewBox={`0 0 ${width} ${height}`}
      stroke="currentColor"
      strokeWidth={strokeWidth}
      width={width}
      height={height}
    >
      {children}
    </svg>
  );
};

And this component accepts children, and everything was ok but now I want to make a ButtonWithIcon component and I want this component to accept children and the SVG markup, but I want to make sure that nothing that is not svg markup would not slip in, it seems that the SVG markup is typed as ReactSVG and when I change the type of children in SvgIcon component to be ReactSvg I get error from typescript

Type 'ReactSVG' is not assignable to type 'ReactNode'.ts(2322)

and actually type SvgFactory is

    interface SVGFactory extends DOMFactory<SVGAttributes<SVGElement>, SVGElement> {
        (props?: ClassAttributes<SVGElement> & SVGAttributes<SVGElement> | null, ...children: ReactNode[]): ReactSVGElement;
    }

so its expecting ReactNode[], so is there any way of differentiating children React.ReactNode for the button from React.ReactNode for SvgIcon component, or maybe to hinder SvgIcon component to only accept svg specific markup?

this is my button so far

import { SvgIcon } from "../SvgIcon";

interface ButtonWithIconProps {
  children: React.ReactNode;
  onClick: (...args: any) => void;
  side: "left" | "right";
  svgMarkup: React.ReactNode;
}

export const ButtonWithIcon = ({
  children,
  onClick,
  side,
  svgMarkup,
}: ButtonWithIconProps) => {
  return (
    <button onClick={onClick} className="">
      {side === "left" && <SvgIcon>{svgMarkup}</SvgIcon>}
      {children}
      {side === "right" && <SvgIcon>{svgMarkup}</SvgIcon>}
    </button>
  );
};

or would that be better practice to just use the Image element? I don't want to have plenty of svg images in my public folder so I would prefer to use markup.

Thanks

CodePudding user response:

You don't have to place SVGs in the public folder - they are components and can be stored as such.

Typically, every SVG is its own file. You also don't need to set default props via destructing; they are overridden if the props object is spread after the default attributes.

import { FC, SVGProps } from 'react';

export const ExampleIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
  <svg
    aria-label="Example icon"
    className="h-6"
    fill="none"
    height={24}
    role="presentation"
    stroke="currentColor"
    strokeWidth={1}
    viewBox="0 0 24 24"
    width={24}
    {...props}
  >
    {/* complete svg contents not children */}
    <path d="" />
  </svg>
);

Example usage

import { ButtonHTMLAttributes, FC } from "react";

interface ButtonWithIconProps {
  children: React.ReactNode;
  leftIcon: React.ReactNode;
  rightIcon: React.ReactNode;
}

export const ButtonWithIcon:FC<ButtonWithIconProps & ButtonHTMLAttributes<HTMLButtonElement>> = ({ children, leftIcon, rightIcon, ...rest }) => (
    <button {...rest}>
      {leftIcon}
      {children}
      {rightIcon}
    </button>
  );

// with props
<ButtonWithIcon leftIcon={<ExampleIcon className="text-red" width={30}/>} />
// without props
<ButtonWithIcon leftIcon={ExampleIcon} />

If you pass the SVG content as children, you create more code than just keeping one icon per file. Also, when you change the icon, you only have to change it in one place.

React has a default button type React.ButtonHTMLAttributes<HTMLButtonElement>. It already includes onClick and other default button HTML attributes.


It's up to you to use images, SVGs, or both.

SVGs are sent as HTML and do not require any extra server fetching. However, big SVGs can add a lot of HTML that is rendered with the original content. SVGs scale to any size without needing anything extra.

Images make an additional server request. However, you can defer them from loading until they are needed. Most sites use multiple image sizes for the same images to deliver an image that matches the user's device without the additional size.

  • Related