Create a React ⚛️ List with Material-UI

Ali Atwa
5 min readJan 5, 2018

--

Note: This tutorial & example won’t work with the new release version
https://www.material-ui.com/#/components/list

Material-UI is is a set of React components that implement Google’s material design to be used with React like Angular. Material-UI has many components so let’s start Lists.

Lists are made up of a continuous column of rows. Each row contains a tile. Primary actions fill the tile, and supplemental actions are represented by icons and text.

What we are going to do is creating a list of some items, these items in JSON format.

Installation

To be able to import components in your project, first install Material-UI package by running the following commands in your Terminal.

npm install material-ui@latest

Starting the component

Go to your project in `src ` directory create new file called `NestedList.js` so we can create a component for list.



function NestedList(props) {
return (

);
}

export default (NestedList);
import React from 'react';

function NestedList(props) {
return (

);
}

export default (NestedList);

Lets’s create JSON function to return items to list.

//NestedList.jsimport React from 'react';function getItems() {
var json = {
"list": [{ "id": 1,
"title": "Google",
"items": [{
"id": 1,
"name": "Android",
"subitems": [{
"id": 1,
"name": "Nougat"
},
{
"id": 2,
"name": "Lollipop"
}]
},
{
"id": 2,
"name": "Chrome"
}
]},
{ "id": 2,
"title": "Apple",
"items": [{
"id": 1,
"name": "Mac"
},
{
"id": 2,
"name": "Iphone",
"subitems": [{
"id": 1,
"name": "Iphone 6"
},
{
"id": 2,
"name": "Iphone 10"
}]
}
]},
{ "id": 3,
"title": "Uber",
"items": [{
"id": 1,
"name": "Eats"
},
{
"id": 2,
"name": "Freight"
}]
}
]};
return json;
}
function NestedList(props) {
render() {
return (

);
}};

export default (NestedList);

the main body the list consists of ListItemText for items & Collapse for sub items both wrapped in List

<List subheader="title"> 
<ListItem button>
<ListItemText primary="Item1" />
</ListItem>
<Collapse component="li" timeout="auto" unmountOnExit>
<List disablePadding>
<ListItem button>
<ListItemText primary="text" />
</ListItem>
</List>
</Collapse>
</List>

for spreading the list with items we define JSON variable for items

const items = getItems();

for looping over array of items, we use method called map() When we call this on an array, it run through all the items in that array, it can also call objects of an item id,title,subitems.

Note: each top-level item should unique “key” attribute that identifies it uniquely.

for every list we put its title list.title in subheader attribute.

render() {
const items = getItems();
return (
<div>
{items.list.map((list) => {
return (
<List key={list.id} subheader={<ListSubheader>{list.title}</ListSubheader>}>
</List>
)
})}</div>)
}

in List we map each item and subitems

{list.items.map((item) => {
return (
<div key={item.id}>
<ListItem button key={item.id}>
<ListItemText primary={item.name} />
</ListItem>
<Collapse key={list.items.id} component="li" timeout="auto" unmountOnExit>
<List disablePadding>
{item.subitems.map((sitem) => {
return (
<ListItem button key={sitem.id}>
<ListItemText key={sitem.id} primary={sitem.name} />
</ListItem>
)
})}
</List>
</Collapse>
</div>

But something will go wrong, cause some items don’t have sub items in JSON data, so to solve this problem we’ll use If statement inline item.subitems != null this statement check if an item has sub items If item has subitems it creates Collapse element to wrap subitems elements.

{item.subitems != null ?  (
<div key={item.id}>
<ListItem button key={item.id}>
<ListItemText primary={item.name} />
</ListItem>
<Collapse key={list.items.id} component="li" timeout="auto" unmountOnExit>
<List disablePadding>
{item.subitems.map((sitem) => {
return (
<ListItem button key={sitem.id}>
<ListItemText key={sitem.id} primary={sitem.name} />
</ListItem>
)
})}
</List>
</Collapse> </div>
) : (
<ListItem button key={item.id}>
<ListItemText primary={item.name} />
</ListItem> )}

Optional: if we want to get arrow icon for subitems menus indicating if the subitems collapsed.

{this.state[item.name] ? <ExpandLess /> : <ExpandMore />}

Note: we used [item.name] so if we have multiple subitems menus each menu has its state.

after each list we want add a divider between them with a unique key

<Divider key={list.id} absolute />

to handle onClick event when clicking on collapsed menu we add this attribute in={this.state[item.name]} to Collapse element.

handelClick function set state for each subitem true or false.

state = { };handleClick = (e) => {
this.setState({ [e]: !this.state[e] });
};

Final code

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import ListSubheader from 'material-ui/List/ListSubheader';
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
import Collapse from 'material-ui/transitions/Collapse';
import ExpandLess from 'material-ui-icons/ExpandLess';
import ExpandMore from 'material-ui-icons/ExpandMore';
import Divider from 'material-ui/Divider';
const styles = theme => ({
root: {
width: '100%',
maxWidth: 360,
background: theme.palette.background.paper,
},
nested: {
paddingLeft: theme.spacing.unit * 4,
},
});
function getItems() {
var json = {
"list": [{ "id": 1,
"title": "Google",
"items": [{
"id": 1,
"name": "Android",
"subitems": [{
"id": 1,
"name": "Nougat"
},
{
"id": 2,
"name": "Lollipop"
}]
},
{
"id": 2,
"name": "Chrome"
}
]},
{ "id": 2,
"title": "Apple",
"items": [{
"id": 1,
"name": "Mac"
},
{
"id": 2,
"name": "Iphone",
"subitems": [{
"id": 1,
"name": "Iphone 6"
},
{
"id": 2,
"name": "Iphone 10"
}]
}
]},
{ "id": 3,
"title": "Uber",
"items": [{
"id": 1,
"name": "Eats"
},
{
"id": 2,
"name": "Freight"
}]
}
]};
return json;
}
class NestedList extends React.Component {
state = { };
handleClick = (e) => {
this.setState({ [e]: !this.state[e] });
};
render() {
const items = getItems();
return (<div>
{items.list.map((list) => {
return (
<List className={classes.root} key={list.id} subheader={<ListSubheader>{list.title}</ListSubheader>}>
{list.items.map((item) => {
return (

<div key={item.id}>
{item.subitems != null ? (
<div key={item.id}>
<ListItem button key={item.id} onClick={this.handleClick.bind(this, item.name)} >
<ListItemText primary={item.name} />
{this.state[item.name] ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse key={list.items.id} component="li" in={this.state[item.name]} timeout="auto" unmountOnExit>
<List disablePadding>
{item.subitems.map((sitem) => {
return (
<ListItem button key={sitem.id} className={classes.nested}>
<ListItemText key={sitem.id} primary={sitem.name} />
</ListItem>
)
})}
</List>
</Collapse> </div>
) : (
<ListItem button onClick={this.handleClickLink.bind(this, item.name)} key={item.id}>
<ListItemText primary={item.name} />
</ListItem> )}
</div>

)
})}
<Divider key={list.id} absolute />
</List>
)
})}
</div>
);
}
}
NestedList.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(NestedList);
Final result

--

--