Seth Lutske
Seth Lutske

Reputation: 10686

Passing props to components rendered dynamically from object

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

Answers (2)

Seth Lutske
Seth Lutske

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

Onur &#214;nder
Onur &#214;nder

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

Related Questions