React / Material-UI detect last ListItem in List - reactjs

I am using material-ui with React. I have a listview that looks like this:
<List>
<Subheader>List Title</Subheader>
<ListItem primaryText="Option One" />
<Divider />
<ListItem primaryText="Option Two" />
<Divider />
<ListItem primaryText="Option Three" />
</List>
There is a divider between each item but none before the first item or after the last. So far so good.
I have just implemented a simple wrapper that renders or does not render a list item based on a user permission. So it looks like this:
<List>
<Subheader>List Title</Subheader>
<Restricted permission={1}>
<ListItem primaryText="Option One" />
</Restricted>
<Divider />
<Restricted permission={2}>
<ListItem primaryText="Option Two" />
</Restricted>
<Divider />
<Restricted permission={4}>
<ListItem primaryText="Option Three" />
</Restricted>
</List>
The Restricted component compares the users permission with the specified one and returns either the child component or null. This all works but obviously retains the dividers if the option is not rendered.
I could wrap the divider within the Restricted component along with the ListItem which in most cases renders what I want but it leaves a divider at the bottom if the last item is not rendered. What I need is a way of saying don't render the divider if this is the last item in the list.
At the moment my plan is to programmatically generate an array of displayable list items and then render this in a loop with the appropriate dividers. But this will mean that I will have to the the decision logic in each component that uses it instead of a single wrapper. That feels wrong.
Is there some aspect of React/material-ui that I have missed that will enable me the achieve what I want in a more elegant way?

I propose the following solution. First move your representation of list to variable
const allOptions = [
{ text: "Option 1", permission: 1 },
{ text: "Option 2", permission: 2 },
{ text: "Option 3", permission: 4 }
]
then use filter to get only permitted options
const permittedOptions = listItems.filter(checkPermission)
use map to create list items
const listItems = permittedOptions.map(option => <ListeItem text={option.text} />)
now put dividers only if there is a next and prev list item
const listItemsWithDividers = [];
listItems.forEach((item, index) => {
listItemsWithDividers.push(item)
if (listItems[index + 1] !== undefined) {
listItemsWithDividers.push(<Divider />)
}
})
and finally render it
<List>
<Subheader>List Title</Subheader>
{listItemsWithDividers}
</List>

A more cost effective approach would look something like this
<div>
<List>
{items.map((item) => (
<div key={item.id}>
<ListItem>
<ListItemText primary={item.name} />
</ListItem>
{item.id !== items.length ? <Divider /> : null}
</div>
))}
</List>
</div>

{items.data.map((item, index) => (
<ListItem button key={index} divider={index < items.length - 1}>
...
</ListItem>
)})

Related

Child component updates parent state unexpectedly in react

I'm using react with MUI. I'm using the MUI List component which each list may have a nested list in it. the problem is when I open a nested list, the parent component gets closed. I don't know what I am doing wrong. You could use this CodeSandbox.
Here is an image of my app: Gif
Your problem is you add onClick on ListItem which contains all other sub-items, so that means whenever you trigger click on any sub-item, you accidentally trigger the main ListItem too. That's why you've seen the main menu item gets collapsed when you click on any sub-item. You can read more about event propagation.
The easiest fix is you should move onClick={handleToggleCollapse} from ListItem to ListItemButton
<ListItem {...listItemProps} sx={{ display: "block" }}>
<ListItemButton onClick={handleToggleCollapse}>
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText primary={text} />
{open ? (
<IoIosArrowUp className="text-lg" />
) : (
<IoIosArrowDown className="text-lg" />
)}
</ListItemButton>
<Collapse in={open} timeout={300}>
<List disablePadding>
<ListItems listItems={nestedItems} />
</List>
</Collapse>
</ListItem>
For event propagation prevention, you should modify handleToggleCollapse like below
function handleToggleCollapse(e: SyntheticEvent) {
e.stopPropagation();
setOpen((prev) => !prev);
}
Sandbox

Unable to solve the Menu bar issue using reactjs?

I'm new to Framework and i have an issue with me sidebar which is showing Table tab as default but it should not show any tab's page as clicked. By default it should show nothing. When we click on a particular tab then it should show color on the tab of the sidebar. If i give -1 then the color is getting disappeared on the menu tab but the Tab's page remain as it is. Is it possible to give pathname. how to fix by giving id ? . If Yes Can anyone help me in fixing this issue?
Here is the code:
<Drawer
className={classes.drawer}
variant="permanent"
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.toolbar} />
<List>
{["table", "home"].map((item, index) => {
const Icon = itemsConfig[item].icon;
return (
<ListItem
component={Link}
to={itemsConfig[item].link}
selected={selectedIndex === index}
onClick={event => handleListItemClick(event, index)}
button
key={item}
>
<ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText primary={itemsConfig[item].text} />
</ListItem>
);
})}
</List>
</Drawer>
Here is whole code: "https://codesandbox.io/s/fervent-browser-ecz7z"
Can anyone help me in this ?
Define your state, that holds the selected index in your <Layout /> component. Define function that manipulate that state and pass it to , <Home /> andcomponents along with the newly defined state. Finally call the function after the initial rendering of the component. For the (class component) use:
componentDidMount() {
this.props.setTabIndex(this.props.index);
}
For the <Table /> use:
useEffect(() => {
props.setTabIndex(props.index);
}, []);
The full result is posted here: https://codesandbox.io/s/cold-mountain-hlhk8?

Material UI List scroll doesn't reset when list data changes

I have a simple list of items.
<List>
{this.state.initialList.map((item, index) => (
<ListItem key={index} button>
<ListItemText primary={item} />
</ListItem>
))}
</List>
If I scroll to the bottom of the list and load a new list, the scroll stays at the bottom. How do I scroll back to the top of the list?
https://codesandbox.io/s/material-demo-l4i3s
In this sandbox, scroll to the bottom, click on "load", the list is showing the last item of the new list.
Not sure if there's a default setting you can set with material-ui to do this. But you definitely can apply some React functionality to get this to work.
See working sandbox: https://codesandbox.io/s/material-demo-8ftqz
Create a ref which would point to the top of the list:
topOfList = React.createRef();
Then in your List Component, return a span at the top of the list. Give that span the ref you crated.
<List component="nav" aria-label="secondary mailbox folders">
{<span ref={this.topOfList} />}
{this.state.initialList.map((item, index) => (
<ListItem key={index} button>
<ListItemText primary={item} />
</ListItem>
))}
</List>
Create an additional handler that scrolls to the element/ref:
scrollToTop = () => {
if (this.topOfList.current) {
this.topOfList.current.scrollIntoView();
}
};
Lastly, use the handler inside the set-state call-back to ensure you scroll-up after the data has loaded.
this.setState({ initialList: newList }, () => this.scrollToTop())
The scroll is caused by the parent div.
You can hold a ref to that div, and on load, use the function scorllTo() to position the scrollbar to the top.
<div ref={ref => this.divRef = ref} class="mylist">
<List component="nav" aria-label="secondary mailbox folders">
{this.state.initialList.map((item, index) => (
<ListItem key={index} button>
<ListItemText primary={item} />
</ListItem>
))}
</List>
</div>
and than in load function:
load = () => {
const newList = [
"new1",
"new2",
"new3",
"new4",
"new5",
"new6",
"new7",
"new8",
"new9",
"new0"
];
this.divRef.scrollTo(0,0);
this.setState({ initialList: newList });
};
You can refer to this CodeSandbox example

Material-UI List (V1-beta): How to pass the list item to onClick function?

I have predefined list item in my dumb component. I am trying to pass the list item to my onClick function so my code can recognize which item was clicked and I can navigate accordingly. Here is my code for the dumb component:
export const mainMenuListItems = (gn, an, onMenuClick) => (
<div>
<ListItem onClick={() => onMenuClick(event, this)} button>
<ListItemIcon>
<HomeIcon />
</ListItemIcon>
<ListItemText primary="Home" />
</ListItem>
<ListItem onClick={() => onMenuClick(event, this)} button>
<ListItemIcon>
<ChartIcon />
</ListItemIcon>
<ListItemText primary="Charts" />
</ListItem>
</div>
);
And this is the code for my smart component:
onMenuClick = (event, type) => {
debugger; //eslint-disable-line
let a = type; //always undefined
alert('Home Clicked');
};
........OTHER CODE............
..............................
<List className={classes.list}>
{mainMenuListItems(
this.props.isGenerationAllowed,
this.props.isAnalystAllowed,
this.onMenuClick,
)}
</List>
I tried to bind "this" in this function call "onMenuClick(event, this)" but no matter what I do, it is always 'undefined'. I am sure it should be pretty easy to access the object where the user has clicked (At least in other languages). Please guide me on how to access the object. I have spent a considerable amount of time on this issue. Any help would be appreciated.

How will prevent triggering navLink/link in react router when click right icon button with nested list item? Material UI

I have a sample code here: ListItem with nested list I want to prevent the link and trigger the nested list. Or I want when I click the rightIconButton only triggers the nested list not the link.
<ListItem
value={3}
primaryText="My Collection"
leftIcon={<Avatar src="http://i.imgur.com/fmvLZGS.png" />}
initiallyOpen={false}
containerElement={<NavLink to="/CollectionPage" />}
nestedItems={[
<ListItem
value={4}
key={1}
primaryText="Wines"/>,
<ListItem
value={5}
key={2}
primaryText="Arts"/>,
<ListItem
value={6}
key={3}
primaryText="Vehicles"/>
]} />
Thank you!

Resources