Is it possible to use the ListContext in own components? - reactjs

I would like to use the dense property of the ListContext to change the ListItemIcon fontSize. When I try to wrap the component in my own and import and use the context, the context is empty. The component is used inside as List/ListItem so it normally should have some values. I suppose it is because the ListContext is only use internally or am I wrong? In the case that I cannot use it what's a possible solution?
import React from 'react';
import MuiListItemIcon, {
ListItemIconProps as MuiListItemIconProps,
} from '#material-ui/core/ListItemIcon';
import ListContext from '#material-ui/core/List/ListContext';
const ListItemIcon: React.ForwardRefExoticComponent<MuiListItemIconProps> = React.forwardRef(
(props, ref) => {
const { children, ...other } = props;
const context = React.useContext(ListContext);
return (
<MuiListItemIcon ref={ref} {...other}>
{context.dense ? React.cloneElement(children, { fontSize: 'small' }) : children}
</MuiListItemIcon>
);
},
);
export default ListItemIcon;
// ...
<List dense>
<ListItem>
<ListItemIcon>
<IcoCreate />
<ListItemIcon>
</ListItem>
<ListItemText primary="Hello world!">
<List>
// ...
I expect the context to have a propery dense that is set to true, but the actual value is empty.

ListContext is considered private. In the Minimizing Bundle Size guide you can find the following:
Be aware that we only support first and second level imports. Anything below is considered private and can cause module duplication in your bundle.
Your import of ListContext is a third-level import (List would be second-level):
import ListContext from '#material-ui/core/List/ListContext';
While it "works" to import it, you are importing a duplicate copy of ListContext rather than the same one used by List which causes this to be a unique context type and is therefore unaffected by the setting of ListContext internal to Material-UI.
There are several ways you could achieve your desired result of dynamically adjusting the icon font-size based on whether or not it is within a dense list:
Create your own List wrapper (in addition to your ListItemIcon wrapper) with its own context type that you use in your ListItemIcon wrapper.
Use CSS (imitating what SvgIcon does internally based on the prop) to change the font-size rather than using the fontSize property as shown in my example below.
import React from "react";
import ReactDOM from "react-dom";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import CreateIcon from "#material-ui/icons/Create";
import { withStyles } from "#material-ui/core/styles";
const SmallIconIfDense = withStyles(theme => ({
root: {
".MuiListItem-dense & .MuiSvgIcon-root": {
fontSize: theme.typography.pxToRem(20)
}
}
}))(ListItemIcon);
function App() {
return (
<>
<List dense>
<ListItem>
<SmallIconIfDense>
<CreateIcon />
</SmallIconIfDense>
<ListItemText primary="Hello dense list!" />
</ListItem>
<ListItem>
<SmallIconIfDense>
<CreateIcon />
</SmallIconIfDense>
<ListItemText primary="Hello second list item!" />
</ListItem>
</List>
<List>
<ListItem>
<SmallIconIfDense>
<CreateIcon />
</SmallIconIfDense>
<ListItemText primary="Hello non-dense list!" />
</ListItem>
<ListItem>
<SmallIconIfDense>
<CreateIcon />
</SmallIconIfDense>
<ListItemText primary="Hello second list item!" />
</ListItem>
</List>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

TypeError passing a function to a React click handler

My (simplified) React component looks like:
import React from 'react';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
export default function MyList(setPanel) {
const handleClick = () => {
console.log(setPanel);
setPanel('support');
};
return (
<List>
<ListItem button key="Support" onClick={handleClick}>
<ListItemText primary="Support" />
</ListItem>
</List>
);
}
and the console log is showing setPanel to be a function but then I'm getting a
TypeError: setPanel is not a function
when I click on the ListItem in the app. What am I missing here?
You didn't destructure the props:
export default function MyList({setPanel})
Since props is an object containing setPanel, you are getting setPanel isn't a function.

Component to call all material-ui Icon in React

I want to implement a component which calls the corresponding icon from material-ui. I've made it work when manually calling it.
import MenuIcon from '#material-ui/icons/Menu';
export const Icon = (props) => {
return (
<div>
<MenuIcon/>
</div>
)
}
The problem is I don't know how to change the import for all icons.
I want to call this component the following way:
<Icon icon="MenuIcon" className="someClass" />
<Icon icon="LocationOn" className="someClass" />
<Icon icon="Notifications" className="OthersomeClass" />
I can't figure out how to import all icons and how to change my Icon component to work for any icon from the material-ui package.
Something like this...
import React from 'react';
import * as IconList from '#material-ui/icons/Menu'; //error
export const Icon = (props) => {
const {icon, className} = props;
return (
<`${icon}` className={className} /> {//error}
)
}
Any ideas?
You should be able to import all the icons using named imports or the star imports (* as).
Star imports should look like this
import * as Icons from "#material-ui/icons";
<Icon icon={Icons.Menu} className="someClass" />
<Icon icon={Icons.AccessAlarmIcon} className="someClass" />
Named imports should look like this
import { Menu, AccessAlarmIcon } from "#material-ui/icons";
<Icon icon={Menu} className="someClass" />
<Icon icon={AccessAlarmIcon} className="someClass" />
You can also refactor your Icon component to utilize the React children prop, that way you can better compose each icon on the Icon component.
So it should look something like this
import React from 'react';
export const Icon = ({ children }) => {
return (
<>{children}</>
)
}
Then you can use it like this
<Icon>
<Menu className="someClass" />
</Icon>
<Icon>
<AccessAlarmIcon className="someClass" />
</Icon>
PS:
Your star imports were from '#material-ui/icons/Menu' as opposed to just '#material-ui/icons' and that caused an error

How to correctly remove padding on Components

I would like to nest a List inside a Card or an Expansion Panel, the problem is that both, the Expansion Panel and List are adding padding to the sides. The result looks really weird...
<Card>
<CardHeader title="Title" subheader="Subheader"/>
<CardContent>
<List>
<ListItem>
<ListItemText
primary={`<--- too much padding`}
/>
</ListItem>
</List>
</CardContent>
</Card>
Here is a running example that shows the issue: https://codesandbox.io/s/material-demo-djzdz
I would like to get rid of the extra padding, I solved the issue temporarily by using the withStyles thingy like this:
import React from "react";
import Card from "#material-ui/core/Card";
import CardHeader from "#material-ui/core/CardHeader";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import { withStyles } from "#material-ui/core/styles";
import MuiCardContent from "#material-ui/core/CardContent";
const NestedCardContent = withStyles(theme => ({
root: {
padding: 0
}
}))(MuiCardContent);
export default function Color() {
return (
<Card>
<CardHeader title="Title" subheader="Subheader" />
<NestedCardContent>
<List>
<ListItem>
<ListItemText primary={`padding is fine`} />
</ListItem>
</List>
</NestedCardContent>
</Card>
);
}
Run it in codesandbox: https://codesandbox.io/s/material-demo-kyx38
As you can see, it works. But it feels absolutely hackish...
I previously tried that mx, px spacing thing described here: https://material-ui.com/customization/spacing/
Without success either - changed nothing.
Furthermore I need this Kind of NestedCardContent in more than one Component. So how would you do it in a clean way? What is considered as a good practice here?
I recommend just removing the CardContent tag entirely. If you look at the source for CardContent, the only thing it does is padding.
If you don't want that padding just do:
import React from "react";
import Card from "#material-ui/core/Card";
import CardHeader from "#material-ui/core/CardHeader";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
export default function Color() {
return (
<Card>
<CardHeader title="Title" subheader="Subheader" />
<List>
<ListItem>
<ListItemText primary={`padding is fine`} />
</ListItem>
</List>
</Card>
);
}
This solution removes the 24px bottom padding as well which may or may not be what you want. If you want to retain the extra bottom padding, then just move your NestedCardContent component into its own file so you can import/reuse it.

How to add a link to a List in material-ui 1.0?

The following messes with the onClick animation (the ListItem turns red):
<List>
<a href="https://www.google.com">
<ListItem button>
<ListItemText primary="Google" />
</ListItem>
</a>
</List>
While adding the link inside ListItem, only makes the transition work if ListItemText is clicked, which is not what I want.
What is the correct way to add a link?
to use with "react-router-dom"
import { Link } from "react-router-dom";
<ListItem button component={Link} to="/design">
the example is based in this section: docs
The easiest way to accomplish this is to make the ListItem a link by using the component prop:
<List>
<ListItem button component="a" href="https://www.google.com">
<ListItemText primary="Google" />
</ListItem>
</List>
That way, the ListItem will be an anchor tag linking to the desired place, but still receive the appropriate styling so that there won't be any visual changes.
The behavior of the component prop is documented here. Note that the href prop will be automatically passed to the anchor tag, as specified by the last line in the props documentation:
Any other properties supplied will be spread to the root element.
I have faced the same issue but maybe a new update in materialUI due to this is not working, There has some tweak as import from import Link from '#material-ui/core/Link';
so it will works
import Link from '#material-ui/core/Link';
<List>
<ListItem button component={Link} href="/dsda">
<ListItemIcon>
<DashboardIcon />
</ListItemIcon>
<ListItemText primary="DashBoard"/>
</ListItem>
</List>
Render Link in material ui Drawer
For usage with Next.js, this worked for me:
import Link from "next/link";
<List>
<Link href="/myUrl" passHref>
<ListItem button component="a">
My Link Text
</ListItem>
</Link>
</List>
For mui V5 + NextJS and if you want to have multiple children inside the list item (for example text and an icon) this worked for me:
import {
Link,
ListItem,
ListItemIcon,
ListItemText,
} from '#mui/material';
import NextLink from 'next/link';
<NextLink href="/some/route" passHref>
<ListItem
button
component={Link}
>
<ListItemIcon>
<SupportIcon />
</ListItemIcon>
<ListItemText
primary="Support"
secondary="Get super friendly support"
></ListItemText>
</ListItem>
</NextLink>
This will give you real a-tags that still have nextjs-routing without page reloads.
import React, { forwardRef } from "react";
// material-ui components
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
// react-router
import { Link as RouterLink } from "react-router-dom";
const ListItemLink = (props) => {
const { icon, primary, to } = props;
const renderLink = React.useMemo(
() =>
forwardRef((itemProps, ref) => (
<RouterLink to={to} ref={ref} {...itemProps} />
)),
[to]
);
return (
<>
<ListItem button component={renderLink}>
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
<ListItemText primary={primary} />
</ListItem>
</>
);
};
export default ListItemLink;
It's actually available in the docs.
ListItem's button prop is now DEPRECATED as you can see in ListItem props (MUI API Docs)
This would be the updated version of the code proposed by #JulesDupont answer:
<List>
<ListItem>
<ListItemButton component="a" href="https://www.google.com">
<ListItemText primary="Google" />
</ListItemButton>
</ListItem>
</List>

Material-ui svg-icons inside another element doesnt align to the center

I'm using the material ui library in my react project, and I have come across a strange issue, when I try to use svg icons inside a button-icon, the icom doesn't align to the center.
for example:
<ListItem key={product.id}
primaryText={product.title}
leftAvatar={<Avatar src={product.img}/>}
rightIcon={<IconButton><RemoveIcon/></IconButton>}/>
for this code I will get the following result:
And for this code:
<ListItem key={product.id}
primaryText={product.title}
leftAvatar={<Avatar src={product.img}/>}
rightIcon={<RemoveIcon/>}/>
I will get the following result :
My question is, how do i get to the result of my second example, but that the icon will we inside another element?
This is kind of late but I recently had the same issue and solved it by wrapping the IconButton component in a custom component and extending the css. You may have to change some other CSS to make it align perfectly but this worked for my use case.
import React, { PropTypes, Component } from 'react';
import IconButton from 'material-ui/IconButton';
const CustomIconButton = (props) => {
const { style } = props;
const additionalStyles = {
marginTop: '0'
};
return(
<IconButton {...props } style={{ ...style, ...additionalStyles }} iconStyle={{ fontSize: '20px' }}/>
);
};
CustomIconButton.PropTypes = {
// listed all the props that IconButton requires (check docs)
};
export default PPIconButton;
This is what a simplified usage of this custom IconButton looks like:
const deleteIconButton = (deleteFunc) => {
return <CustomIconButton
touch={true}
tooltip="Delete"
tooltipPosition="top-right"
onTouchTap={deleteFeed}
iconClassName="fa fa-trash"
/>;
};
class MyList extends Component {
render() {
return (
<div>
<List>
<ListItem value={ i } primaryText="My List Item" rightIcon={ deleteIconButton(() => this.props.deleteFeed(i) } />
) }
</List>
</div>
);
}
}
Passing the styles down to the inner element worked for me:
return <SvgIcon style={this.props.style} />
check this code, working fine for me
import React from 'react';
import List from 'material-ui/List';
import ListItem from 'material-ui/List/ListItem';
import Delete from 'material-ui/svg-icons/action/delete';
const MenuExampleIcons = () => (
<div>
<List style={{width:"300px"}}>
<ListItem primaryText="New Config" leftIcon={<Delete />} />
<ListItem primaryText="New Config" rightIcon={<Delete />} />
</List>
</div>
);
export default MenuExampleIcons;

Resources