I have an array of items, which are defined by a string and an icon component:
type MyItem = {
Title: string;
Icon: React.ElementType;
};
export const MyItems: MyItem[] = [
{ Title: "Inbox", Icon: InboxIcon },
{ Title: "Users", Icon: UserIcon },
{ Title: "Settings", Icon: SettingsIcon },
];
Now I want to display them within a component:
<>
{MyItems.map((item, index) => (
<h1>item.Title</h1>
// render icon component here: item.Icon
))}
</>
How can I render the icon component (without changing the array structure)?
I'm guessing here that your icons are essentially React Components. If so then you can try out the code below:
<>
{MyItems.map((item, index) => (
<>
<h1>item.Title</h1>
{item.Icon}
</>
))}
</>
I'm using React-icons in my ReactJS project and I just wanted to loop (by Map method) the specific icons in each JSX field when data is render.
In other word, I want this{`<${e.contact.icons}/>`}in JSX code.
Here is my code section:-
Here is, I import some icons for React icons.
import { FaBeer, Fa500Px, FeAccusoft } from "react-icons/fa";
Here is a data array which I want to render in JSX.
const data = [
{
contact: [
{
title: 'contact',
icons: 'FaBeer',
},
{
title: 'contact',
icons: 'Fa500Px',
},
{
title: 'contact',
icons: 'FaAccusoft',
},
],
},
]
And this is my component in down below. Which I'm using icons. You get little idea what I want to do.
const contact = () => {
return (
<>
{data.map((e, i) => {
return (
<>
<div className="text-area">
<span> {`<${e.contact.icons}/>`} </span>
</div>
</>
);
})}
</>
);
};
export default contact;
I'm trying to use like this{`<${e.contact.icons}/>`}, but is not working. When I see in browser. It's look like this.
<FaBeer/>
<Fa500Px/>
<FaAccusoft/>
It's just return like a text, but I want to get icons.
Any suggestion ?
You cannot use strings to represent React Component Types, instead you can use the imported ComponentType itself.
import { FaBeer, Fa500Px, FaAccusoft } from "react-icons/fa";
// here is data for I want to show
const data = [
{
contact: [
{
title: "contact",
subtitle: "get in touch",
icons: FaBeer,
},
{
title: "contact",
subtitle: "get in touch",
icons: Fa500Px,
},
{
title: "contact",
subtitle: "get in touch",
icons: FaAccusoft,
},
],
},
];
const Contact = () => {
return (
<>
{data.map((e, i) => {
const Icon = e.contact.icons;
return (
<>
<div className="text-area">
<h1 className="title">{e.contact.title}</h1>
<h2 className="subtitle">{e.contact.subtitle}</h2>
<span><Icon /></span>
</div>
</>
);
})}
</>
);
};
export default Contact;
Note how the rendering of the icon changes as well. I have assigned the icon component to a variable Icon instead of calling <e.contact.icons/> directly because React expects components to start with a capital letter.
The Icon variable will be a React component (either a function component or a class component) so you can call that component by using standard JSX <Icon /> syntax. You can also pass any of the react-icons props, for example: <Icon color="#FF0000" size={24}/>.
https://codesandbox.io/s/fervent-goldwasser-y83cn?file=/src/App.js
import { FaBeer, Fa500Px, FaAccusoft } from "react-icons/fa";
// here is data for I want to show
const data = [
{
contact: [
{
title: "contact",
subtitle: "get in touch",
icons: FaBeer
},
{
title: "contact",
subtitle: "get in touch",
icons: Fa500Px
},
{
title: "contact",
subtitle: "get in touch",
icons: FaAccusoft
}
]
}
];
const contact = () => {
return (
<>
{data.map((e, i) => {
return (
<>
{e.contact.map((e, i) => {
return (
<div className="text-area" key={i}>
<h1 className="title">{e.title}</h1>
<h2 className="subtitle">{e.subtitle}</h2>
<span>
<e.icons />
</span>
</div>
);
})}
</>
);
})}
</>
);
};
export default contact;
Well, the option of importing FaIcon-s and putting them into "data" array looks pretty nice:
import { FaBeer, Fa500Px, FaAccusoft } from "react-icons/fa";
const data = [
{
contact: [
{
title: "contact",
subtitle: "get in touch",
icons: FaBeer,
},
...
On the other hand possibility of generating components "dynamically" by their string name could be still implemented.
Firstly, I find usefull following article: React / JSX Dynamic Component Name
Next, I've created a new FaIconDynamic component:
import {
AiFillAccountBook,
AiFillAlert,
AiFillAlipayCircle,
AiFillAndroid,
} from 'react-icons/ai';
export const FaIconDynamic = ({ type }) => {
const FaIcon = components[type];
return <FaIcon></FaIcon>;
};
const components = {
AiFillAccountBook,
AiFillAlert,
AiFillAlipayCircle,
AiFillAndroid,
};
And then that's pretty easy to generate any registered above fa-icon, f.e.:
function App() {
return (
<>
<FaIconDynamic type={'AiFillAccountBook'} />
<FaIconDynamic type={'AiFillAlert'} />
<FaIconDynamic type={'AiFillAlipayCircle'} />
<FaIconDynamic type={'AiFillAndroid'} />
</>
);
}
Of course, both approaches have their pros and cons and could be more benefitial in some situations over each other
I have got the answer. I know the answer is not an ideal one, but it's work for me just now. The problem with the answer is that. We imported all the fonts from react-icons. So, I guess, as we will grow the project larger. It will decrease the performances and the major factor of could be react icons.
And also as Mr.Ali Shefaee describe in the comment section.
import React from "react";
import { render } from "react-dom";
import * as FontAwesome from "react-icons/lib/fa";
Now that section we could use two type of method.
First one :-
Here we import the all icons and use the function to get specific icon which we want
const Icon = props => {
const { iconName, size, color } = props;
const icon = React.createElement(FontAwesome[iconName]);
return <div style={{ fontSize: size, color: color }}>{icon}</div>;
};
const App = () => {
const iconString = "FaBeer";
const beer = React.createElement(FontAwesome[iconString]);
return (
<div>
<Icon iconName={"FaBeer"} size={12} color="orange" />
</div>
);
};
render(<App />, document.getElementById("root"));
And Second :-
const App = () => {
const iconString = "FaBeer";
const beer = React.createElement(FontAwesome[iconString]);
return (
<div>
<FontAwesome.FaBeer />
<div style={{ fontSize: 24, color: "orange" }}>{beer}</div>
</div>
);
};
render(<App />, document.getElementById("root"));
Here is the Demo:- Codesandbox.
Thank to〈Evie.Codes〉.
It seems that the current answers already addresses the problem, so this only attempts to add small improvement for the solution. Here is an approach I tried in a similar situation.
Simplified demo on: stackblitz
This will keep data the same as posted in the question as it might need to be fetched:
const data = [
{
contact: [
{
title: 'contact',
icons: 'FaBeer',
},
{
title: 'contact',
icons: 'Fa500Px',
},
{
title: 'contact',
icons: 'FaChrome',
},
],
},
];
Define an object with the imported icons as static data:
import { FaBeer, Fa500Px, FaChrome } from 'react-icons/fa';
const icons = { FaBeer, Fa500Px, FaChrome };
In the output, the icon can taken out from static data, and rendered on condition:
const Contact = () => {
return (
<>
{data.map((e, i) => (
<React.Fragment key={i}>
{e.contact.map((item, index) => {
const Icon = icons?.[item.icons];
return (
<div key={index} className="text-area">
<span>{Icon && <Icon size="3em" color="hotpink" />}</span>
</div>
);
})}
</React.Fragment>
))}
</>
);
};
export default contact;
import { FaBeer, Fa500Px, FeAccusoft } from "react-icons/fa";
note: there is a typo in the name of the icon you imported .. it should be FaAccusoft
my suggestion for your question is to store the Icon components themselves in the object property .. so instead of storing it as string: "FaBeer" ... store it as a component: <FaBeer /> directly inside the object property .. like this:
const data = [
{
contact: [
{
title: "contact-1",
icons: <FaBeer />
},
{
title: "contact-2",
icons: <Fa500Px />
},
{
title: "contact-3",
icons: <FaAccusoft />
}
]
}
];
and then you can simply loop over them
const Contact = () => {
return (
<>
{data.map((e, i) => {
return (
<>
{e.contact.map((c) => {
return (
<div className="text-area">
{c.title}
<span>
{c.icons} // you simply call it like this and the icon will be rendered
</span>
</div>
);
})}
</>
);
})}
</>
);
};
You can also use Parser() from html-react-parser. https://github.com/remarkablemark/html-react-parser
const parse = require('html-react-parser');
{parse(`<${e.contact.icons}/>`)};
My code is pretty straightforward but leads to the following warning in the console:
index.js:1 Warning: Duplicated key 'undefined' used in Menu by path []
This is so odd since all the relevant elements do have unique keys...?!
import { useState } from 'react';
import {
Layout,
Menu
} from 'antd';
import './navigation.less';
const { Sider } = Layout;
const items = [
{
id: 'abc',
title: "MenuItem 1",
navKey: 1,
disabled: false
},
{
id: 'def',
title: "MenuItem 2",
navKey: 2,
disabled: false
},
{
id: 'ghj',
title: "MenuItem 3",
navKey: 3,
disabled: true
},
]
const MenuItem = ({navKey, title, disabled}) => {
return (
<Menu.Item key={navKey} disabled={disabled}>
{title}
</Menu.Item>
)
}
const menu = items.map((item) => <MenuItem key={item.id} {...item} />);
const Navigation = () => {
const [collapsed, setCollapsed] = useState(false);
return (
<Sider collapsible collapsed={collapsed} onCollapse={() => setCollapsed(!collapsed)} width={260}>
<Menu theme="light" mode="inline">
<Menu.Divider />
{menu}
<Menu.Divider />
</Menu>
</Sider>
);
}
export default Navigation;
Your menu syntax is wrong.
The change below will get it to work.
const menu = items.map((item) => MenuItem(item))
I am quite new to React and I need to make a dashboard, where I will have a table with some data.
One of the column in the table is clickable and when I click on a particular cell, it will take that cell value and display more details about that item "in a new tab".
Now this is easy with a browser, where you open a new tab on link click. But this is an app i am making on chromium. more of a desktop app.
But I do not want a new window whole together. I need a new tab panel to open with the previous table still there in one tab, and the details in the new tab.
so when I go back to the previous tab and click on another item, it opens a third tab with the details of this item.
Example below (Sorry I am not allowed to insert pictures yet. Please click the link to see them.)
1st picture with initial table.
First Picture With the initial table
Now, if I click on Accountant, a new tab should appear as in second image:
Second image with a new tab opened
I think I found a solution to this. I am pasting it here for someone looking for a solution.
import React, { useState, useCallback, useEffect } from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import { Tabs, Tab, IconButton } from "#material-ui/core";
import CloseIcon from "#material-ui/icons/Close";
import TabContainer from "../TabContainer/index.jsx";
const styles = theme => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper
},
colorPrimary: {
color: "red"
}
});
export const TabsDemo = ({
tabs,
selectedTab = 1,
onClose,
tabsProps = { indicatorColor: "primary" },
...rest
}) => {
const [activeTab, setActiveTab] = useState(selectedTab);
const [activetabs, setActiveTabs] = useState([]);
// useEffect(() => {
// if (activeTab !== selectedTab) setActiveTab(selectedTab);
// }, [setActiveTab, selectedTab, activeTab]);
// const handleChange = useCallback(event => setActiveTab(event.target.value), [
// setActiveTab,
// ]);
useEffect(() => {
setActiveTabs(tabs);
}, [tabs]);
const handleChange = useCallback((event, activeTab) => {
setActiveTab(activeTab);
}, []);
const handleClose = useCallback(
(event, tabToDelete) => {
event.stopPropagation();
const tabToDeleteIndex = activetabs.findIndex(
tab => tab.id === tabToDelete.id
);
const updatedTabs = activetabs.filter((tab, index) => {
return index !== tabToDeleteIndex;
});
const previousTab =
activetabs[tabToDeleteIndex - 1] ||
activetabs[tabToDeleteIndex + 1] ||
{};
setActiveTabs(updatedTabs);
setActiveTab(previousTab.id);
},
[activetabs]
);
return (
<>
<div>
<Tabs value={activeTab} onChange={handleChange}>
{activetabs.map(tab => (
<Tab
key={tab.id}
value={tab.id}
label={
typeof tab.label === "string" ? (
<span>
{" "}
tab.label
{tab.closeable && (
<IconButton
component="div"
onClick={event => handleClose(event, tab)}
>
<CloseIcon />
</IconButton>
)}
</span>
) : (
tab.label
)
}
/>
))}
</Tabs>
{activetabs.map(tab =>
activeTab === tab.id ? (
<TabContainer key={tab.id}>{tab.component}</TabContainer>
) : null
)}
</div>
</>
);
};
TabsDemo.propTypes = {
classes: PropTypes.object.isRequired,
tabs: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
id: PropTypes.number.isRequired,
component: PropTypes.object.isRequired,
closeable: PropTypes.bool.isRequired
}).isRequired
).isRequired
};
export default withStyles(styles)(TabsDemo);
Below is the link of a code sandbox that you can use to understand this fully :
https://codesandbox.io/s/mui-closeable-tab-gw2hw?file=/src/components/tabsdemo/index.jsx:0-3123
This happens when I reload directly the /login and /account page. Those two pages has Material-UI components.
Also here's how my express server looks like. Server.js
Ok so here's what I did to temporarily fix this problem. I only showed the Material-UI component after firing the componentDidMount lifecycle method. I'm using component state for this. Here's how it works:
class AccountNav extends Component {
constructor(props){
super(props);
this.state = {
load: false
}
}
componentDidMount(){
this.setState({ load: true });
}
render(){
const { activeItem } = this.props;
const { load } = this.state;
if(!load) return <div></div>;
else{
return(
<List style={{width: 250}}>
<ListItem button divider="true" style={activeItem == 'profile' ? styles.listHoverStyle : {}}>
<Link prefetch as="/account/profile" href="/account?page_slug=profile">
<ListItemText primary='Your Profile' />
</Link>
</ListItem>
<ListItem button style={activeItem == 'edit' ? styles.listHoverStyle : {}}>
<Link prefetch as="/account/edit" href="/account?page_slug=edit">
<ListItemText primary="Edit Profile" />
</Link>
</ListItem>
</List>
);
}
}
}
// 1 . Warning: prop classname did not match. Material ui with React Next.js
// 2 . Use your customization css here
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
title: {
flexGrow: 1,
},
my_examle_classssss: {
with: "100%"
}
}));
// 3 . Here my Component
const My_Example_Function = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<Container>
<Examle_Component> {/* !!! Examle_Component --> MuiExamle_Component*/}
</Examle_Component>
</Container>
</div>
);
}
export default My_Example_Function
// 4. Add name parameter to the makeStyles function
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
title: {
flexGrow: 1,
},
my_examle_classssss: {
with: "100%"
},
}), { name: "MuiExamle_ComponentiAppBar" });
{/* this is the parameter you need to add { name: "MuiExamle_ComponentiAppBar" }
The problem will probably be resolved
if the name parameter matches the first className in the Warning: you recive..
EXAMPLE :
Warning: Prop `className` did not match.
Server: "MuiSvgIcon-root makeStyles-root-98"
Client: "MuiSvgIcon-root makeStyles-root-1"
The name parameter will be like this { name: "MuiSvgIcon" }
*/ }