bigpotato
bigpotato

Reputation: 27507

Styled components + Storybook: SVG Icon is not re-rendering when I pass new styles

I am trying to create a SvgIcon component that I pass an SVG to through the props.

Because of the way React handles SVGs, when they're imported as ReactComponents (e.g. import { ReactComponent as ArrowIcon } from "icons/arrow.svg") I have to define my component in a dynamic way.

This is what I have so far:

const styleSvgIcon = ({ theme, stroke, fill, size, icon: Icon }) => styled(
  Icon
).attrs({
  viewBox: `0 0 24 24`,
})`
  path {
    ${stroke && `stroke: ${calculateColor({ color: stroke, theme })};`}
    ${fill && `fill: ${calculateColor({ color: fill, theme })};`}
  }
  height: ${calculateSize(size)};
  width: ${calculateSize(size)};

`;

const SvgIcon = ({ stroke, fill, size, icon }) => {
  const theme = useTheme();

  const StyledIcon = styleSvgIcon({ theme, stroke, fill, size, icon });
  console.log('styledddddddddd', StyledIcon);  // prints the styled component with the new styles, but on the page nothing changes
  return <StyledIcon />;
};

SvgIcon.propTypes = {
  stroke: PropTypes.oneOf(Object.values(SVG_ICON_COLORS)),
  fill: PropTypes.oneOf(Object.values(SVG_ICON_COLORS)),
  size: PropTypes.oneOf(Object.values(SVG_ICON_SIZES)),
  icon: PropTypes.elementType.isRequired,
};

export default SvgIcon;

Here's my story definition:

import React from 'react';
import SvgIcon from './SvgIcon';
import * as all_icons from '../icons';

import { SVG_ICON_SIZES, SVG_ICON_COLORS } from '../constants/variants';

export default {
  title: 'media/SvgIcon',
  component: SvgIcon,
  argTypes: {
    size: {
      type: 'select',
      options: Object.values(SVG_ICON_SIZES),
      defaultValue: SVG_ICON_SIZES.SMALL,
    },
    stroke: {
      type: 'select',
      options: Object.values(SVG_ICON_COLORS),
      defaultValue: SVG_ICON_COLORS.PRIMARY,
    },
    fill: {
      type: 'select',
      options: Object.values(SVG_ICON_COLORS),
      defaultValue: SVG_ICON_COLORS.PRIMARY,
    },
  },
};

const Template = (args) => {
  const icons = Object.values(all_icons);
  // return icons.map((SvgComponent, i) => (
  return icons
    .slice(0, 2)
    .map((SvgComponent, i) => (
      <SvgIcon key={i} {...args} icon={SvgComponent} />
    ));
};

export const Small = Template.bind({});
Small.args = {
  size: SVG_ICON_SIZES.SMALL,
};

export const Medium = Template.bind({});
Medium.args = {
  size: SVG_ICON_SIZES.MEDIUM,
};

export const Large = Template.bind({});
Large.args = {
  size: SVG_ICON_SIZES.LARGE,
};

export const XL = Template.bind({});
XL.args = {
  size: SVG_ICON_SIZES.XL,
};

This works fine locally, but when I deploy my app and try to change the props through Storybook, nothing gets updated.

enter image description here

However, in production, the controls do nothing.

enter image description here

Upvotes: 2

Views: 1183

Answers (1)

bigpotato
bigpotato

Reputation: 27507

I FIGURED IT OUT!!!

So because I was just setting the styles without using the props like this:

size: ${calculate(size)};

styled-components somehow knows and therefore doesn't add listeners to updated props. So my StyledIcon just becomes a static component that does not update when new styles are passed in since they are just set in stone.

Instead, passing them as props and accessing them through props adds those listeners

const SvgIcon = ({ stroke, fill, size, icon: Icon }) => {
  const StyledIcon = styled(Icon).attrs({
    viewBox: `0 0 24 24`,
  })`
    path {
      stroke: ${({ stroke, theme }) =>
        calculateColor({ color: stroke, theme })};
      fill: ${({ fill, theme }) => calculateColor({ color: fill, theme })};
    }
    height: ${({ size }) => calculateSize(size)};
    width: ${({ size }) => calculateSize(size)};
  `;

  return (
    <div>
      <StyledIcon size={size} fill={fill} stroke={stroke} />,{size},{fill},
      {stroke}
      <StyledArrowUpIcon stroke={stroke} size={size} fill={fill} />
    </div>
  );
};

Upvotes: 1

Related Questions