How to avoid DRY code with React Components - javascript

Currently Using React with Material UI v1.0 in implementing a list but I don't want to repeat my code.
The existing Code looks like this.
import List from 'material-ui/List';
import DashboardIcon from 'material-ui-icons/Dashboard';
import BuildIcon from 'material-ui-icons/Build';
import Listings from './BarComponents';
function SideBar() {
return (
<div>
<List>
<ListItem button>
<ListItemIcon>
<DashboardIcon />
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItem>
<ListItem button>
<ListItemIcon>
<BuildIcon />
</ListItemIcon>
<ListItemText primary="Control Panel" />
</ListItem>
</List>
</div>
);
}
export default SideBar;
I want to get avoid repeating creating the list items so i've created a new file and passed the props into, code is below.
import React from 'react'
import { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
export default function Listings(props) {
return(
<div>
<ListItem button>
<ListItemIcon>
<props.icon />
</ListItemIcon>
<ListItemText primary={props.prim} />
</ListItem>
</div>
);
}
And also this
<Listings icon={DashboardIcon} prim="Dashboard" />
<Listings icon={BuildIcon} prim="Build" />
Into the original file for a replacement of
<ListItem button>
<ListItemIcon>
<DashboardIcon />
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItem>
<ListItem button>
<ListItemIcon>
<BuildIcon />
</ListItemIcon>
<ListItemText primary="Control Panel" />
</ListItem>
Is the best way to pass a component e.g though
and call it via Thanks in advance.

You can use a dynamic component.
renderElement(name, props = {}) {
var MyComponent = name
return <MyComponent {...props} />;
}
render() {
return(
<div>
<ListItem v-for="item in list" key={item.id} button={item.button}>
<ListItemIcon>
{renderElement(props.icon)}
</ListItemIcon>
<ListItemText primary={props.prim} />
</ListItem>
</div>
);
}

Related

React material Ui tooltip with list item

enter image description here
I want to implement react material ui tooltip with customizes tooltip component .
which will use tooltip ,list item,list component from react material ui
I tried using list item
Install MUI dependencies
cd into project root directory and run npm install #mui/material #emotion/react #emotion/styled and npm install #mui/icons-material
Import the required components
import { Box, List, Tooltip, ListItem, ListItemButton, ListItemIcon, ListItemText, Divider } from '#mui/material';
import MailIcon from '#mui/icons-material/Mail';
import ModeIcon from '#mui/icons-material/Mode';
import * as React from 'react';
Write the List component
From https://mui.com/material-ui/react-list/ and https://mui.com/material-ui/react-tooltip/\
The two first item have a tooltip here : "Inbox" and 'Drafts"
export default function BasicList() {
return (
<Box sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
<List>
<Tooltip title="Inbox">
<ListItem disablePadding>
<ListItemButton>
<ListItemIcon>
<MailIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItemButton>
</ListItem>
</Tooltip>
<Tooltip title="Drafts">
<ListItem disablePadding>
<ListItemButton>
<ListItemIcon>
<ModeIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItemButton>
</ListItem>
</Tooltip>
</List>
<Divider />
<List>
<ListItem disablePadding>
<ListItemButton>
<ListItemText primary="Trash" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton component="a" href="#simple-list">
<ListItemText primary="Spam" />
</ListItemButton>
</ListItem>
</List>
</Box>
);
}

how to render component from .map() in react

So im trying to not duplicate code and probably over complicating this but im very curious if there is a way for a system like this to work
<Drawer anchor="right" open={sideBarOpen} onClose={toggleSideBar}>
<List className={classes.sideBar}>
{[
["test1", <LockOpenIcon />],
["test2", <LockOpenIcon />],
["test2", <LockOpenIcon />],
].map(({ text, icon }, index) => (
<Fragment key={index}>
<ListItem button>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
<Divider />
</Fragment>
))}
</List>
</Drawer
where I map over an array of pairs of [text, iconComponent] and then render the iconComponent in the following element. this is producing no errors (but also not rendering anything in the ) and if theres not a way then thats cool but any help would be appreciated.
Yes, it's possible, and you've mostly done it right. You just used object destructuring ({text, icon}) where you should have used iterable destructuring ([text, icon]):
<Drawer anchor="right" open={sideBarOpen} onClose={toggleSideBar}>
<List className={classes.sideBar}>
{[
["test1", <LockOpenIcon />],
["test2", <LockOpenIcon />],
["test2", <LockOpenIcon />],
].map(([ text, icon ], index) => (
<Fragment key={index}>
<ListItem button>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
<Divider />
</Fragment>
))}
</List>
</Drawer>
However, if the values are hardcoded like that, you might consider abstracting the repeated part of that into its own component:
const MyListItem = React.memo(({text, icon}) => (
<ListItem button>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
));
(The React.memo is just because I figure this doesn't change unless text or icon changes.)
Then it's:
<Drawer anchor="right" open={sideBarOpen} onClose={toggleSideBar}>
<List className={classes.sideBar}>
<MyListItem text="test1" icon={<LockopenIcon/>} />
<Divider />
<MyListItem text="test2" icon={<LockopenIcon/>} />
<Divider />
<MyListItem text="test3" icon={<LockopenIcon/>} />
</List>
</Drawer>

React/Material UI Drawer With Nested List Items Closes on Click

I am using Material-UI for my React project and having issues with getting the drawer to function properly when I add in nested list items. Everything was working great until I added those in. I believe the root cause is that by clicking on the dropdown menu and changing the state of the list item to be open I'm causing the application to re-render. Not sure how to solve this.
Problem: Nested list items close the drawer automatically when you click the top level. The user then has to open the drawer again to see the list items in the drop down.
Desired Functionality: The user clicks the menu item to open the drawer. The user can click on "Leadership Triad" and see the menu items within while the drawer stays open. When the user clicks off, the drawer closes.
Code Sandbox
https://codesandbox.io/s/material-ui-nested-menu-forked-qqdiv
My Code
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import Drawer from "#material-ui/core/Drawer";
import List from "#material-ui/core/List";
import Divider from "#material-ui/core/Divider";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import HomeIcon from "#material-ui/icons/Home";
import AccountCircle from "#material-ui/icons/AccountCircle";
import ExitToAppIcon from "#material-ui/icons/ExitToApp";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import PeopleIcon from "#material-ui/icons/People";
import BusinessIcon from "#material-ui/icons/Business";
import Menu from "#material-ui/core/Menu";
import MenuItem from "#material-ui/core/MenuItem";
import { Link as RouterLink } from "react-router-dom";
import Collapse from "#material-ui/core/Collapse";
const useStyles = makeStyles(theme => ({
root: { flexGrow: 1 },
menuButton: { marginRight: theme.spacing(2) },
title: { flexGrow: 1 },
list: { width: 250 },
nested: { paddingLeft: theme.spacing(4) },
}));
const Header = () => {
const [drawerOpen, setDrawerOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const [leadershipTriadMenuOpen, setLeadershipTriadMenuOpen] = useState(false);
const handleLeadershipTriadClick = () => {
setLeadershipTriadMenuOpen(!leadershipTriadMenuOpen);
};
const handleClick = event => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const toggleDrawer = () => {
setDrawerOpen(!drawerOpen);
};
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
onClick={() => toggleDrawer()}
>
<MenuIcon />
<Drawer
anchor="left"
open={drawerOpen}
onClose={() => toggleDrawer()}
>
<div className={classes.list}>
<List>
<ListItem button component={RouterLink} to="/">
<ListItemIcon>
<HomeIcon color="primary" />
</ListItemIcon>
<ListItemText primary="Home" />
</ListItem>
<ListItem
button
onClick={() => handleLeadershipTriadClick()}
>
<ListItemIcon>
<HomeIcon color="primary" />
</ListItemIcon>
<ListItemText primary="Leadership Triad" />
{leadershipTriadMenuOpen ? (
<ExpandLess />
) : (
<ExpandMore />
)}
</ListItem>
<Collapse
in={leadershipTriadMenuOpen}
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemIcon>
<HomeIcon />
</ListItemIcon>
</ListItem>
</List>
</Collapse>
<ListItem button>
<ListItemIcon>
<InboxIcon color="primary" />
</ListItemIcon>
<ListItemText primary="Testing" />
</ListItem>
<ListItem button>
<ListItemIcon>
<InboxIcon color="primary" />
</ListItemIcon>
<ListItemText primary="Testing" />
</ListItem>
</List>
<Divider />
<List>
<ListItem
button
component={RouterLink}
to="/admin/companies"
>
<ListItemIcon>
<BusinessIcon color="primary" />
</ListItemIcon>
<ListItemText primary="Companies" />
</ListItem>
<ListItem
button
component={RouterLink}
to="/admin/users"
>
<ListItemIcon>
<PeopleIcon color="primary" />
</ListItemIcon>
<ListItemText primary="Users" />
</ListItem>
</List>
<Divider />
<List>
<ListItem button component={RouterLink} to="/profile">
<ListItemIcon>
<AccountCircle color="primary" />
</ListItemIcon>
<ListItemText primary="Profile" />
</ListItem>
<ListItem button component={RouterLink} to="/logout">
<ListItemIcon>
<ExitToAppIcon color="primary" />
</ListItemIcon>
<ListItemText primary="Logout" />
</ListItem>
</List>
</div>
</Drawer>
</IconButton>
<Typography variant="h6" className={classes.title}>
Leadership Program
</Typography>
<IconButton color="inherit" onClick={handleClick}>
<AccountCircle />
</IconButton>
<Menu
id="admin-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem
component={RouterLink}
to="/profile"
onClick={handleClose}
>
Profile
</MenuItem>
<MenuItem
onClick={handleClose}
component={RouterLink}
to="/logout"
>
Logout
</MenuItem>
</Menu>
</Toolbar>
</AppBar>
</div>
);
};
export default Header;
Have you tried surrounding the IconButton with a ListItemSecondaryAction?
<ListItemSecondaryAction>
<IconButton onClick={handleOnClick}>
{openState ? <ExpandLess /> : <ExpandMore />}
</IconButton>
</ListItemSecondaryAction>
I was trying to do something similar, where a list item had two clickable areas with two different actions: click the ListItem (to go to a page) or click the IconButton (to expand the ListItem) to display nested ListItems. Without this ListItemSecondaryAction, when I would click on a list item, it would both route and expand the ListItem, which was not desirable.
Adding the SecondaryAction separated click actions of the ListItem and the IconButton. The documentation isn't great, so it took me a while to understand the purpose, but it's here.

Returning nightmare with .map() inside of a .map()

I've been at this for a while. So I have some nested navigation json that I am using. The top level navigation is loading fine (the nav.map) once I move further down the rabbit hole I find myself not returning the top level or the sub level navigation. Everything compiles successfully. Am I just missing it?
return(
<List component="nav" className={classes.root}>
{nav.map(function(element) {
<ListItem
button
onClick={handleClick}
id={element.toplevel}
key={element.toplevel}
>
<ListItemText primary={element.toplevel} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>;
return element.children.map(function(child) {
return (
<Collapse timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemIcon>
<StarBorder />
</ListItemIcon>
<ListItemText primary={child.name} />
</ListItem>
</List>
</Collapse>
);
});
})}
</List>
);
Make sure that your map callback functions actually return the jsx code. Your return statements are not set right.
One way that is commonly used in react-land to make the jsx code more readable is the arrow function syntax. This way you get rid of the return statements and return the whole function body (it's just syntactic sugar).
Next thing: be aware of your closing tags. I just assumed that your list item of the element object closes after the ListItemText tag and that your second map function opens a new list item after your element ListItem. jsx only lets you return one root tag at a time. This is why (as the comment below has suggested) using an empty <> ... </> tag pair as a root element will solve this issue.
return (
<List component="nav" className={classes.root}>
{data.nav.map((element) => (
<>
<ListItem
button
onClick={handleClick}
id={element.toplevel}
key={element.toplevel}
>
<ListItemText primary={element.toplevel} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
{
element.children.map((child) => (
<Collapse timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemIcon>
<StarBorder />
</ListItemIcon>
<ListItemText primary={child.name} />
</ListItem>
</List>
</Collapse>
))
}
</>
))}
</List>
);
Sandbox link: https://codesandbox.io/s/material-demo-uv7c7?fontsize=14&hidenavigation=1&theme=dark
Try this, i have restructured it a bit.
<List component="nav" className={classes.root}>
{nav.map(function(element) {
return(
<ListItem
button
onClick={handleClick}
id={element.toplevel}
key={element.toplevel}
>
<ListItemText primary={element.toplevel} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
{element.children.map(function(child) {
return (
<Collapse timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemIcon>
<StarBorder />
</ListItemIcon>
<ListItemText primary={child.name} />
</ListItem>
</List>
</Collapse>
)
})
}
}))}
</List>
);

react-router: unable to route to child components

i am building an online database using react and the routes have been set up such that if user is not authenticated, they will be redirected to the login page.
However, i am facing difficulties for the nested routes. i think some parts of the implementation is incorrect.
2 problems:
In order for the "404" (handled by NotFound component) page to work, exact props has to be used in <Route/>
However if exact props is used, the <Links/> found in FixedDrawer component will not work. Clicking any of them will direct to "404" page.
What am i doing wrong here?
For simplicity's sake, i will only be sharing the rendering parts of the components.
Code as below:
App.js (PageShell is only used for animating page transitions)
import LoginPage from "./pages/LoginPage";
import Dashboard from "./pages/Dashboard";
import NotFound from "./pages/NotFound";
.
.
.
render() {
const childProps = {
isAuthenticated: this.state.isAuthenticated,
userHasAuthenticated: this.userHasAuthenticated
};
return (
<div className="App">
<Switch>
<Route path="/login" exact component={PageShell(LoginPage)} />
<Route path="/" exact component={PageShell(Dashboard)} />
<Route component={NotFound} />
</Switch>
</div>
);
Dashboard.js
render() {
return (
<div>
<NavBar user={this.state.user} />
<FixedDrawer handleSetCategory={this.handleSetCategory} />
<Switch>
<Route path="/athletes" render={() => <AthletesDataTable />} />
<Route path="/races" render={() => <RacesDataTable />} />
</Switch>
</div>
);
}
FixedDrawer.js
class FixedDrawer extends Component {
constructor() {
super();
this.state = {};
}
render() {
return (
<Drawer variant="permanent" elevation={10}>
<List component="nav" style={{ top: 65 }}>
<ListItem button>
<ListItemIcon>
<FaceIcon />
</ListItemIcon>
<Link to="/athletes" style={{ textDecoration: "none" }}>
<ListItemText primary="Athletes" />
</Link>
</ListItem>
<ListItem button>
<ListItemIcon>
<GroupIcon />
</ListItemIcon>
<Link to="/teams" style={{ textDecoration: "none" }}>
<ListItemText primary="Teams" />
</Link>
</ListItem>
<ListItem button>
<ListItemIcon>
<DateRangeIcon />
</ListItemIcon>
<Link to="/meets" style={{ textDecoration: "none" }}>
<ListItemText primary="Meets" />
</Link>
</ListItem>
<ListItem button>
<ListItemIcon>
<PoolIcon />
</ListItemIcon>
<Link to="/races" style={{ textDecoration: "none" }}>
<ListItemText primary="Races" />
</Link>
</ListItem>
<ListItem button>
<ListItemIcon>
<AssignmentIcon />
</ListItemIcon>
<Link to="/results" style={{ textDecoration: "none" }}>
<ListItemText primary="Race Results" />
</Link>
</ListItem>
</List>
</Drawer>
);
}
}
"Link" component will try to match the exact route. Since, "/meets", "/teams" doesn't match the routes you provided, it will give 404 page.
return (
<div className="App">
<Navbar />
<Sidebar />
<Switch>
<Route path="/login" exact component={PageShell(LoginPage)} />
<Route path="/:val" component={PageShell(SomePage)} />
<Route path="/" exact component={PageShell(Dashboard)} />
<Route component={NotFound} />
</Switch>
<Footer />
</div>
);
I'll suggest you write the route component as above. And in the componentDidMount of "SomePage" component, you can get the value of "val" as mentioned in the path above in "this.props.match" object and render the "SomePage" component as per the routes you want.
Ex: In the didMount function of SomePage:
componentDidMount(){
this.setState({val: this.props.match.params.val})
}
render(){
return(
<div>
{
this.state.val === 'teams' ? <TeamsComponent /> : null
}
{
this.state.val === 'meets' ? <MeetsComponent /> : null
}
</div>
)
}

Categories

Resources