lucide-icons / lucide

Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons.
https://lucide.dev
ISC License
10.96k stars 500 forks source link

Icons stop working (black silhouettes) in React Native after over the air (OTA) updates #1478

Closed alwyntan closed 1 year ago

alwyntan commented 1 year ago

Prerequisites

Step to reproduce

Install lucide react native as per the documentations. During development, the builds work fine, and production builds work fine **until an over the air update is served through Codepush. (expo updates could have the same issue too)

Actual behavior

Icons black out and only shows the silhouettes after an over the air update. It might be a JS bundling issue that is resulting in this.

Any message or error

No message or error

Resources

See screenshots below:

Before an OTA update: image

After an OTA update: IMG_1325

alwyntan commented 1 year ago

For some reason this seems to be related to a problem where SVG child attributes are not inheriting the parent's properties (fill, stroke, etc). On development it works, but once an OTA update is released, it breaks.

And for the currentColor to work as a color, the parent Svg component seems to need the prop color to be defined, eg: <Svg color="red" />, for the currentColor of the child to work properly.

What I've done to make this work is explicitly copy the properties over to the SVG children in the function createLucideIcon for OTA updates to work. Happy to open a PR.

Related issues in react-native-svg: https://github.com/software-mansion/react-native-svg/issues/1284 https://github.com/software-mansion/react-native-svg/issues/1287

Proposed solution/patch solution I'm using right now:

import { forwardRef, createElement, ReactSVG, ReactNode, FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import * as NativeSvg from 'react-native-svg';
import defaultAttributes from './defaultAttributes';
import type { SvgProps } from 'react-native-svg';
type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][]

export interface LucideProps extends SvgProps {
  size?: string | number
  absoluteStrokeWidth?: boolean
}

const createLucideIcon = (iconName: string, iconNode: IconNode) => {
  const Component = forwardRef(
    ({ color = 'currentColor', size = 24, strokeWidth = 2, absoluteStrokeWidth, children, ...rest }: LucideProps, ref) =>
      createElement(
        NativeSvg.Svg as unknown as string,
        {
          ref,
          ...defaultAttributes,
          width: size,
          height: size,
          stroke: color,
          strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
+         color, 
          ...rest,
        },
        [
          ...iconNode.map(([tag, attrs]) => {
            const upperCasedTag = (tag.charAt(0).toUpperCase() + tag.slice(1)) as keyof (typeof NativeSvg);
-           return createElement(NativeSvg[upperCasedTag] as FunctionComponent<Record<string, string>>, attrs);
+           // add this to the defaultAttributes file
+           const defaultChildProps = {
+             stroke: defaultAttributes.stroke, // or we can just have it use the "color" prop directly, and we wont have to add the color line above
+             strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
+             strokeLinecap: defaultAttributes.strokeLinecap,
+             strokeLinejoin: defaultAttributes.strokeLinejoin
+           }
+           return createElement(NativeSvg[upperCasedTag] as FunctionComponent<Record<string, string>>, { ...defaultChildProps, ...attrs }); // add the properties explicitly here, can consider adding ...rest here
          }),
          ...(
            (Array.isArray(children) ? children : [children]) || []
          ),
        ],
      ),
  );

  Component.propTypes = {
    color: PropTypes.string,
    size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    strokeWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  };

  Component.displayName = `${iconName}`;

  return Component;
};

export default createLucideIcon;
ericfennis commented 1 year ago

@alwyntan Thanks for reporting this. Feel free to open a PR!