5
votes

I am using React to help manage my SVG icons:

const Trash = ({
  fill, width, height,
}) => (
  <svg width={ `${width}px` } height={ `${height}px` } xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 17.92 20.42">
    <path style={ { fill } } d="M14,20.3H3.91a0.5,0.5,0,0,1-.5-0.47l-0.89-14A0.5,0.5,0,0,1,3,5.35H14.9a0.5,0.5,0,0,1,.5.53l-0.89,14A0.5,0.5,0,0,1,14,20.3Zm-9.63-1h9.16l0.83-13H3.56Z" />
    <rect style={ { fill } } x="5.69" y="5.84" width="1" height="13.98" transform="translate(-0.68 0.35) rotate(-3.1)" />
    ...
  </svg>
);

I manage changing colours based on state in a wrapper:

const makeIcon = Icon => props => <IconWrapper { ...props }><Icon /></IconWrapper>;
export const TrashIcon = makeIcon(Trash);

The wrapper looks like this (heavily abridged):

class IconWrapper extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isActive: false,
      isHovered: false,
    };    
   }

  handleClick(e) {
    if (this.props.onClick) {
      this.props.onClick(e);
    }

    this.setState({
      isActive: !this.state.isActive,
    });
  }

  ...

  render() {
    const Icon = React.cloneElement(
      this.props.children,
      {
        isActive: this.state.isActive,
        fill: this.getFill(),
        width: this.props.width,
        height: this.props.height,
      },
    );

    return (
      <button
        onClick={ this.handleClick }
        ref={ this.setWrapperRef }
        width={ this.props.width }
        height={ this.props.height }
      >
        { Icon }
      </button>);
  }
}

So, I wish to add my icon to a container component. I can add it like this:

<TrashIcon fill="#fff" activeFill="#000" />

That works, but I'm OCD about styling being in my container. What I would like to do is create a styled component that pushes down those styling attributes:

// styled.js
const StyledTrashIcon = styled(TrashIcon).attrs({
  fill: colors.white,
  activeFill: colors.sushi,
  hoverFill: colors.sushi,
})`
 // other styles
`;

But when I attempt to use StyledTrashIcon, I get:

Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.

Changing to:

const StyledTrashIcon = styled(TrashIcon()).attrs({

throws this error:

Cannot create styled-component for component: [object Object]

1
Can you try it this way: const StyledTrashIcon = styled(TrashIcon)().attrs({Vishal
Try SVGR, it generates JSX from SVG files and allows controlling the props of the <svg> using react syntaxvsync
You should define the styled-component after your component, if your component uses const - const StyledTrashIcon = styled(Trash)`...`vsync

1 Answers

0
votes

I am now styling like this, where Arrow is a functional component that returns an SVG:

import styled, { ThemeContext } from "styled-components";
import { Arrow } from "src/icons/Arrow";
import React, { useContext } from "react";

export const StyledArrow = () => {
  const themeContext = useContext(ThemeContext);

  return (
    <Arrow
      color={themeContext.elements.arrow.color}
      width="20px"
      height="20px"
    />
  );
};