Following Gatsby's doc on Creating Dynamic Navigation in Gatsby I created a barebones menu and wanted to see if I can add React Icons' components to it:
gatsby-config.js (stripped down)
menuLinks:[
{
name:'home',
icon: 'AiFillHome',
link:'/'
},
{
name:'contact',
icon: 'AiFillHome',
link:'/contact'
}
]
after finding out that Gatsby errors out when I tried creating an external menuLinks
file as a module, example:
failed approach as module:
import React from 'react'
// React Icons
import { AiFillHome } from 'react-icons/ai'
const Menu = [
{
name:'home',
icon: <AiFillHome />,
link:'/'
},
{
name:'contact',
icon: <AiFillHome />,
link:'/contact'
}
]
export default Menu
I dont have an issue in my query:
const data = useStaticQuery(
graphql`
query {
site {
siteMetadata {
menuLinks {
name
icon
link
}
}
}
}
`,
)
in a file I've passed down menu props to from my query and then map.
(stripped down file):
{menu.map((menuItem, key) => {
return (
<Link key={key} to={menuItem.link}>
<div>
<span className="icon">{`<${menuItem.icon} />`}</span>
{menuItem.name}
</div>
</Link>
)
})}
my icon
doesn't render. I've also tried:
<span className="icon" component={menuItem.icon} />
doesn't work. I've also tried:
<span className="icon"><menuItem.icon /></span>
and:
<span className="icon">{React.createElement(menuItem.icon)}</span>
Research:
In Gatsby how can I pass an icon's component name to menuLinks
and later render it?
After the answer the implementation of:
{menu.map(({link, name, icon: Icon}) => {
return (
<Link key={name} to={link}>
<div>
<span className="icon"><Icon /></span>
{name}
</div>
</Link>
)
})}
the browser doesn't render the React Icon Component and when examining the Elements panel I get:
<span class="icon">
<aifillhome></aifillhome>
</span>
Also note I do get a terminal error of:
warning 'AiFillHome' is defined but never used no-unused-vars
which was expected but that leads to me wondering how do I bring in:
import { AiFillHome } from 'react-icons/ai'
Implementation of:
const AssetComponentsTuple = new Map([
[`home`, <AiFillHome />],
[`otherAsset`, <AiFillHome2 />],
])
export default AssetComponentsTuple
would throw an error of:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
but the approach gave me an idea for this workaround of:
{menu.map(({link, name, icon}) => {
return (
<Link key={name} to={link}>
<div>
<span className="icon"><IconRetrieve icon={icon} /></span>
{name}
</div>
</Link>
)
})}
and that allowed me to pass the icon string and ternary for the component.
IconRetrieve
component:
import React from 'react'
import PropTypes from 'prop-types'
// React Icons
import { AiFillHome } from 'react-icons/ai'
import { MdCall } from 'react-icons/md'
import { BiErrorCircle } from 'react-icons/bi'
const IconRetrieve = ({ icon }) => {
const mapIcons = new Map([
[`home`, <AiFillHome />],
[`contact`, <MdCall />],
[`default`, <BiErrorCircle />],
])
return mapIcons.has(icon) ? mapIcons.get(icon) : mapIcons.get('default')
}
IconRetrieve.propTypes = {
icon: PropTypes.string.isRequired,
}
export default IconRetrieve
The positive of this approach is I can create SVG components or any component in that matter and return it based on the icon string.