Reputation: 10686
I have an object full of components:
const Icons = {
// commonly used darksky icon names
"clear-day": <ClearDayIcon />,
"clear-night": <ClearNightIcon />,
"rain": <RainMediumIcon />,
"snow": <SnowIcon />,
"sleet": <RainHeavyIcon />,
"wind": <WindyDayIcon />,
// etc..
}
Each component is really just an svg wrapped as a react component. But I need it to receive props from the place where its called, especially the className
prop.
What I want to be able to do is call these components from my other components in the following manner:
<WeatherIcon icon={icon} className="some-class" />
Where the icon prop will determine which icon component is chosen. So I tried this:
const WeatherIcon = props => Icons[ props.icon ]
So this works partially, as I can write <WeatherIcon icon={'clear-night'} />
, and the correct component is rendered. However, there's no way to pass any other props from my WeatherIcon
component down through each Icon
. For example, writing <WeatherIcon icon={'clear-night'} className="some-class" />
clearly does not pass the className
prop (or any other prop) down through to each individual component. I tried doing something like this:
const Icons = {
"clear-day": props => <ClearDayIcon {...props} />,
"clear-night": props => <ClearNightIcon {...props} />,
// etc..
}
But this doesn't work because now I'm returning a Component
rather than a <Component />
. I saw the solutions in the question Passing props to dynamically loaded components, but these all suggest calling the component like { Icons['clear-day'](className: 'some-class', anotherProp: 'some-prop') }
. I feel like this is not very elegant. There must be a way to write it as a <WeatherIcon icon={'some-icon'} className={'some-class'} someProp={'some-prop'} />
, and have the props filter down correctly. (I do realize that all the props would filter all the way down to my SVG component - that's fine). I feel like theres a higher order component waiting to be written here, but right now its eluding me.
Thanks for reading
Upvotes: 0
Views: 887
Reputation: 10686
I figured out that this works too - write the object containing the components as a function of props, which returns the object. Then you can use a {...props}
in each component:
const Icons = props => ({
"clear-day": <ClearDayIcon {...props} />,
"clear-night": <ClearNightIcon {...props} />,
"rain": <RainMediumIcon {...props} />,
etc.
})
Then the wrapper component looks like this:
const WeatherIcon = props => Icons(props)[ props.icon ]
Pretty simple - I knew I was close. I'm not sure if this is considered a higher order component (HOC). It is a component that returns another component based on its props, with new props attached. Does that count as an HOC?
Upvotes: 0
Reputation: 1042
I'm not 100% sure about if this will fulfill your requirement but I would probably try something like this;
const Icons = {
"clear-day": ClearDayIcon,
"clear-night": ClearNightIcon,
"rain": RainMediumIcon,
"snow": SnowIcon,
"sleet": RainHeavyIcon,
"wind": WindyDayIcon,
// etc..
}
const Icon = ({icon, ...rest}) => {
const IconComponent = Icons[icon]
if(!IconComponent) {
// Or throw an exception maybe.
// At least print some console warnings in development env.
return null;
}
return <IconComponent {...rest} />
}
With this way, you can select one of your Icon components and pass any prop to it. And when you want to use it, you can use it like this;
// ...
<Icon icon="clear-day" className="some-class" />
// ...
Upvotes: 1