prismo
prismo

Reputation: 1899

How To Pass a Ref to a SVG Component in React

I have a svg component that is declared like so:


import {ReactComponent as Icon} from 'someplace.svg'

function SomeComponent(props){
   const [someState, setSomeState] = useState(0)
   const iconRef = useRef(null)

   useEffect(() => {
      //always prints null
      console.log(iconRef.current) 
   }, [someState])

return <div>
 <button onClick={() => setSomeState(prev => prev + 1)}>{someState}</button>
 <Icon ref={iconRef}/>
</div>
}

The problem here is that iconRef will always return null. I think this is because it is declared as a component so the ref would need to be forwarded directly to the svg tags but how do I do that?

Any ideas?

Upvotes: 6

Views: 14606

Answers (2)

Omar Omeiri
Omar Omeiri

Reputation: 1833

I'm not sure if this works in create-react-app, but it is dead anyway.

First you need to install svgr.

npm i @svgr/webpack

Add a svgr config file.

./.svgrrc.js

module.exports = {
  ref: true,
}

And a webpack config.

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/i,
        use: ['@svgr/webpack'],
      },
    ],
  },
}

If using next insert the webpack config in next.config.js.

const webpack = require('webpack');

module.exports = {
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'],
    });
    return config;
  },
};

Then you can do something like:

import Icon from "@icons/icon.svg";

const IconWithRef () => {
  const ref = useRef<SVGSVGElement | null>(null);

  return (
      <Icon width="50" ref={ref} />
  );
};

You can check this sandbox for a working version.

UPDATE

Using this, I ran into some problems while using svg paths directly into .css files, because webpack resolves it as a react component. The workaround I could find to make both it work is by changing the webpack settings like so.

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/i,
        type: 'asset',
        resourceQuery: /url/,
      },
      {
        test: /\.svg$/i,
        resourceQuery: { not: [/url/] },
        use: ['@svgr/webpack'],
      },
    ],
  },
}

And then using svg's paths in .css like:

.svg-background {
  background-image: url('../path/to/svg/image.svg?url');
}

Notice the ?url query param at the end.

Using the option issuer: /\.[jt]sx?$/, like in the docs, did not work for me.

Upvotes: 3

Ponchique
Ponchique

Reputation: 221

This could be fixed in 3 steps:

  1. Get the SVG icon as a React Component in your codebase.
  2. Pass setRef to it.

Example:

Grab the SVG code into the component, like this:

const CloseIcon = (props) => (
  <svg
    width="38"
    height="38"
    viewBox="0 0 38 38"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
  >
    <circle cx="19" cy="19" r="18" stroke="#AFAFAF" stroke-width="2"></circle>
    <path
      d="M13.0548 13.336L24.9868 25.9185"
      stroke="#AFAFAF"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
    ></path>
    <path
      d="M24.9862 13.3365L13.0542 25.9189"
      stroke="#AFAFAF"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
    ></path>
  </svg>
)

export default CloseIcon

Then, in the Parent Component, where you use this icon, set its ref property, like:


...

const closeIconRef = createRef()
...

<CloseIcon
   style={{ position: 'absolute', top: 18, right: 18, cursor: 'pointer' }} 
   setRef={closeIconRef}
/>

Then add setRef to the tag in your SVG component:

const CloseIcon = ({ setRef }) => (
  <svg
    ref={setRef}
    width="38"
    height="38"
    viewBox="0 0 38 38"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
  >
...

You are done!

*The important thing to remember: the child nodes still are non-referenced, so it works if there are no shapes on the way of a hit. You can attach a ref to every child tho.

Upvotes: 2

Related Questions