1

I have the following component:

import React, { ReactElement } from 'react'

interface Props {
  icon: JSX.Element; // should accept only svg
  fill?: string;
  stroke?: string;
}

export default function AppIcon(props: Props): ReactElement {
  // TODO:
  // Replace: svg *[fill] = props.fill
  // Replace: svg *[stroke] = props.stroke 
}

As you can see, the component accepts an icon of JSX.Element (not sure how can I strict it to SVG only).

After that, it should look in the icon tree for children who has fill and stroke attribute and replace accordingly. Meaning, if there's a path with fill, it will replace it with the given fill. If a path doesn't have a fill, then it won't be added.

How can I achieve this behavior?

Eliya Cohen
  • 10,716
  • 13
  • 59
  • 116

1 Answers1

2

I think I have managed to get what you want working:

import React, { ReactElement } from "react";
import "./AppIcon.css";

interface Props {
    children: JSX.Element;
    fill?: string;
    stroke?: string;
}

export default function AppIcon(props: Props): ReactElement {
    return (
        // @ts-ignore
        <div className="AppIcon" style={{ "--fill": props.fill, "--stroke": props.stroke }}>
            {props.children}
        </div>
    );
}

In AppIcon.css:

.AppIcon > svg * {
    fill: var(--fill);
    stroke: var(--stroke);
}

Using the component:

...
<AppIcon fill="blue" stroke="yellow">
    <svg>
        <circle cx="50" cy="50" r="40" strokeWidth="3" />
    </svg>
</AppIcon>
...

Explanation:

  • Firstly the icon property on the Props interface should be children. React will populate that property with any children of the Component. As far as I understand there is no way to limit that to a certain tag name unfortunately.

  • It then renders the child under a div tag. Here I give that div tag a className for identification later and I give the div a style with two css custom properties where their values match the fill and stroke provided by the props. (TypeScript does not like this as they are not defined so thus we have // @ts-ignore)

  • Those properties can be accessed by any descendants / children of the div element. So in the adjoining stylesheet we set elements in the svg to use the variables set with the var() keyword in the stylesheet.

demo in codesandbox

Ben
  • 3,160
  • 3
  • 17
  • 34
  • I haven't thought of using variables. Nice approach! but still, I'm interested in finding a reactive way. Thanks anyway! – Eliya Cohen Mar 19 '20 at 09:02
  • @EliyaCohen Hmm fill and stroke should be reactive (see my update in codesandbox). Is passing down state to fill what you mean by reactive? Unfortunately I believe this the only way to do this. React cannot do analysis and mutation of the children prop. You could involve jQuery or DOM calls if that mutation is what you want. Otherwise the approach would be to have a component for each of your icons and write jsx into the svg? Could you clarify where my answer missed as I may be able to provide a better solution? – Ben Mar 19 '20 at 11:35
  • if what you're saying is indeed a fact, then there's no more need to clarify. If react can't access the element's children then I can accept this as the right answer. – Eliya Cohen Mar 19 '20 at 12:55