skip to Main Content

As I am new to React (I come from Angular), I am not sure if there is a "React" way of doing this, but I am importing a json file, which has a property called content which contains an svg string, it looks similar to this:

{
  "content": "<svg width="24">...</svg>"
}

I then have a function to render the content of that json:

export interface IIconsObject { content: string; }

export interface IIconsProps extends ComponentPropsWithoutRef<'div'> {
  icon: IIconsObject;
  size?: 'small' | 'medium';
  color?: string | 'default' | 'class';
}

export const Icon = (data: IIconsProps) => {
  let content = data.icon.content;
  const size = data.size === 'small' ? 16 : 24;

  content = content.replace(/height="24"/g, `height="${size}"`);
  content = content.replace(/width="24"/g, `width="${size}"`);

  return <span dangerouslySetInnerHTML={{ __html: content }} />;
};

Right now I am doing regex to replace the width/height, but is there a better way to do this, such as using a dom selector that selects the svg and then maybe using setAttribute like svg.setAttribute('width', size)?

3

Answers


  1. This is fine with dangerouslySetInnerHTML. I think you should keep it this way if you need to use dangerouslySetInnerHTML.

    More React way:
    useRef for access to span html node. spanRef.current will contain regular DOM element.
    useEffect to make modification after content or size changes and react renders updated nodes.

    export const Icon = (data: IIconsProps) => {
      let content = data.icon.content;
      const size = data.size === 'small' ? 16 : 24;
      const spanRef = useRef();
      
      useEffect(() => {
        // manipulate spanRef.current 
      }, [content, size]);
    
      return <span dangerouslySetInnerHTML={{ __html: content }} ref={spanRef} />;
    };
    

    Note that you might need to remove [content, size] if your component gets rerendered without changes to those values because dangerouslySetInnerHTML will overwrite previous modifications.

    Login or Signup to reply.
  2. Yes, in React, there are more robust ways to handle SVG manipulation than using regex replacements on strings. Instead of manipulating the SVG string directly, you can convert the SVG string into a React component. This approach provides greater flexibility, allowing you to utilize React’s capabilities to manage attributes and state.

    Here’s a step-by-step approach to refactor your current method:

    Step 1: Convert SVG String to React Component
    Instead of importing SVG as a string, consider using it directly as a React component. This can be achieved using libraries like @svgr/webpack, which can be set up in your project’s webpack configuration to import SVG files as React components.

    If you prefer to continue using SVG as a string within your JSON (as your current project setup might require it), you can parse the SVG string to a React element using dangerouslySetInnerHTML as you did, or use a utility function to convert it into a React component dynamically.

    Step 2: Dynamically Adjust SVG Properties
    Once you have the SVG loaded as a React component, you can easily modify its properties such as width and height. Here’s how you can refactor your component:

    import React from 'react';
    
    export interface IIconsObject { content: string; }
    
    export interface IIconsProps extends React.ComponentPropsWithoutRef<'div'> {
      icon: IIconsObject;
      size?: 'small' | 'medium';
      color?: string | 'default' | 'class';
    }
    
    export const Icon = (data: IIconsProps) => {
      const size = data.size === 'small' ? 16 : 24;
      
      // Function to parse SVG string and return a React element
      const createSvgElement = (svgString: string) => {
        const parser = new DOMParser();
        const svgDoc = parser.parseFromString(svgString, "image/svg+xml");
        const svgElement = svgDoc.documentElement;
    
        svgElement.setAttribute('width', `${size}`);
        svgElement.setAttribute('height', `${size}`);
        
        return svgElement.outerHTML;
      };
    
      // Use the function to transform the SVG content
      const svgContent = useMemo(
        () => createSvgElement(data.icon.content),
        [data.icon.content, size]
      );
    
      return <span dangerouslySetInnerHTML={{ __html: svgContent }} />;
    };
    

    useMemo caches the result for given data.icon.content and size.

    Login or Signup to reply.
  3. Might be late to the party but I’m here to suggest an approach using a library. At the end it would be very similar to @myrrtle’s answer but the benefit is that here you wouldn’t have to implement the DOM parsing logic yourself.

    You could use html-react-parser which helps, as the name suggests, parsing HTML strings to React components. I prefer something like this because you have even more control with the parsing where implementing something like sanitizing the parsed HTML with isomorphic-dompurify or others becomes even easier.

    In any case, something like this could do the trick:

    import { useMemo } from "react";
    import parser, { attributesToProps, domToReact } from "html-react-parser";
    
    export const Icon = ({ content }) => {
      const parsedIcon = useMemo(
        () =>
          parser(content, {
            replace: (domNode) => {
              if (!domNode.attribs) {
                return null;
              }
    
              const props = attributesToProps(domNode.attribs);
              const children = domNode.children
                ? domToReact(domNode.children)
                : null;
    
              return (
                <svg {...props} width={50} height={50}>
                  {children}
                </svg>
              );
            },
          }),
        [content]
      );
    
      return parsedIcon;
    }
    

    You can check the following CodeSandbox for a demo on how to use this:

    Edit SVG String to React

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search