Material-ui: nestedItems in List are not indented

Created on 25 Jul 2016  路  10Comments  路  Source: mui-org/material-ui

Problem description

I programmatically create a <List> and some <ListItem>s has nestedItems, the list is created correctly, though the nested items are not indented at all.
I guess that the problem is related on how I create the List, as I interfere with the components hierarchy that material-ui expects, is there another way to do that? or it's an issue with material-ui?

_update 25/07/2016_: the nestedLevel property on the ListItem components is always 0, so the marginLeft CSS property is consequently 0

Steps to reproduce

I need to create a List from an XML file that is something like this:

<navitem>
  <navitem>
    <navitem />
  </navitem>
</navitem>

So I uses a main component:

var ConfigurationNav = React.createClass({
  render: function() {
    var items = [];
    var _this = this;
    this.props.nav.find('> navitem').each(function(i) {
      items.push(<ConfigurationNavItem item={this} />);
    });

    return (
      <List>
        {items}
      </List>
    );
  }
});

and a subcomponent:

var ConfigurationNavItem = React.createClass({
  render: function() {
    var nested = [];
    for (var i=0; i<this.props.item.children.length; i++) {
      nested.push(<ConfigurationNavItem item={this.props.item.children[i]} />);
    }

    return (
      <ListItem key={this.props.item.id}
          primaryText={this.props.item.getAttribute('label')}
          nestedItems={nested}
          />
    );
  }
});

The list is created with <ConfigurationNav nav={this.state.nav}/>

Versions

  • Material-UI: 0.15.2
  • React: 15.2.1
  • Browser: 45.0.2 - Ubuntu
List

Most helpful comment

Just stumbled over this issue, when encountering the same problem and looking for a solution.

Basically, the indentation in _material-ui_'s List component is achieved via the ListItem component. It has a property nestedLevel with a default value of 0. When ListItem elements are inserted via the nestedItems attribute, they are wrapped into a NestedList element which increments the parents nestedLevel and forwards it to its children. Thus, only ListItem components and other components which have a nestedLevel property and use it to control their own indentation are nicely indented.

If you just want to wrap a ListItem into another component, you could give it a property nestedLevel (with the default value 0) that is forwarded to the wrapped ListItem. As opposed to the workaround proposed by @markoshust, this works also if the element is inserted in deeper or changing nesting levels.

import React from 'react';

var NestedComponent = React.createClass({
  propTypes: {
    // Define the nestedLevel property
    nestedLevel: React.PropTypes.number,
  },
  getDefaultProps() {
    return {
      // Set the default value for the nestedLevel property
      nestedLevel: 0,
    };
  },
  render() {
    return (
      // Create the wrapped ListItem element and forward the nestedLevel property
      <ListItem key={/*The key of the list item*/}
          primaryText='The primary text of the list item'
          nestedItems={/*Whatever you want to nest into the list item*/}
          nestedLevel={nestedLevel}
          />
    );
  }
});

If you do not want to wrap a ListItem component you need to implement the indentation yourself, e.g. using margin-left. This is the same thing that ListItem does. In order to allow for deeper nesting levels, also provide your component with a nestedLevel property. In _material-ui_ the nestedLevelDepth is retrieved from this.context.muiTheme.listItem.nestedLevelDepth, but you might define your own indentation depth. The code might look as follows:

import React from 'react';

var NestedComponent = React.createClass({
  propTypes: {
    // Define the nestedLevel property
    nestedLevel: React.PropTypes.number,
  },
  getDefaultProps() {
    return {
      // Set the default value for the nestedLevel property
      nestedLevel: 0,
    };
  },
  contextTypes: {
    // Define the requirement for a muiTheme object in the context
    muiTheme: React.PropTypes.object.isRequried,
  },
  render() {
    const {listItem} = this.context.muiTheme;
    const {nestedLevel} = this.props;
    const styles = {
      innerDiv: {
          marginLeft: nestedLevel * listItem.nestedLevelDepth,
      },
    };
    return (
      // Indent the inner div or any other wrapped element
      <div style={styles.innerDiv}></div>
    );
  }
});

All 10 comments

Same problem here. Have you found a solution beside giving the subelements a margin-left?

not yet, now I put aside this problem and kept the list without indent. that's pretty awful though.

I took a look at material-ui source code and probably we can hack the property nestedLevel that is used to compute the margin-left, but i didn't try yet

It seems the ListItem component's nestedItems prop only accepts ListItem components for proper indentation, I believe that is the root of this issue...

It should really accept any component, as I'm wrapping my children ListItem's in container components, some of which relay on a base div to work properly.

FYI, a simple workaround for this right now is to add this prop to the parent ListItem component:

nestedListStyle={{ marginLeft: 18 }}

If you are in fact using ListItem children but need to wrap them within div components or something similar, you can also pass this prop to the children:

nestedLevel={1}

There is a bug somewhere in the code. I looked around and couldn't find exactly where this was happening, as by looking at the code it seems to be set correctly https://github.com/callemall/material-ui/blob/master/src/List/ListItem.js#L52 -- perhaps this is happening somewhere else.

I was abble to achieve this via recursive function.

const recursiveList = (node, index) => {
  const children = node.children.map(
    (node, i) => recursiveList(node, i)
  );

  const item = (
    <ListItem
      key={index}
      primaryText={node.title}
      primaryTogglesNestedList={true}
      nestedItems={children}
    />
  );

  return item;
};

class Page extends Component {
  render() {
    const tree = [
      {
        title: "Category1",
        children: [
          {
            title: "foo",
            children: []
          },
          {
            title: "bar",
            children: [
              {
                title: "so deep",
                children: []
              },
              {
                title: "inception",
                children: []
              }
            ]
          }
        ]
      }
    ];
    return (
      <List>
        {tree.map(
          (node, i) => recursiveList(node, i)
        )}
      </List>
    );
  }
}

Just stumbled over this issue, when encountering the same problem and looking for a solution.

Basically, the indentation in _material-ui_'s List component is achieved via the ListItem component. It has a property nestedLevel with a default value of 0. When ListItem elements are inserted via the nestedItems attribute, they are wrapped into a NestedList element which increments the parents nestedLevel and forwards it to its children. Thus, only ListItem components and other components which have a nestedLevel property and use it to control their own indentation are nicely indented.

If you just want to wrap a ListItem into another component, you could give it a property nestedLevel (with the default value 0) that is forwarded to the wrapped ListItem. As opposed to the workaround proposed by @markoshust, this works also if the element is inserted in deeper or changing nesting levels.

import React from 'react';

var NestedComponent = React.createClass({
  propTypes: {
    // Define the nestedLevel property
    nestedLevel: React.PropTypes.number,
  },
  getDefaultProps() {
    return {
      // Set the default value for the nestedLevel property
      nestedLevel: 0,
    };
  },
  render() {
    return (
      // Create the wrapped ListItem element and forward the nestedLevel property
      <ListItem key={/*The key of the list item*/}
          primaryText='The primary text of the list item'
          nestedItems={/*Whatever you want to nest into the list item*/}
          nestedLevel={nestedLevel}
          />
    );
  }
});

If you do not want to wrap a ListItem component you need to implement the indentation yourself, e.g. using margin-left. This is the same thing that ListItem does. In order to allow for deeper nesting levels, also provide your component with a nestedLevel property. In _material-ui_ the nestedLevelDepth is retrieved from this.context.muiTheme.listItem.nestedLevelDepth, but you might define your own indentation depth. The code might look as follows:

import React from 'react';

var NestedComponent = React.createClass({
  propTypes: {
    // Define the nestedLevel property
    nestedLevel: React.PropTypes.number,
  },
  getDefaultProps() {
    return {
      // Set the default value for the nestedLevel property
      nestedLevel: 0,
    };
  },
  contextTypes: {
    // Define the requirement for a muiTheme object in the context
    muiTheme: React.PropTypes.object.isRequried,
  },
  render() {
    const {listItem} = this.context.muiTheme;
    const {nestedLevel} = this.props;
    const styles = {
      innerDiv: {
          marginLeft: nestedLevel * listItem.nestedLevelDepth,
      },
    };
    return (
      // Indent the inner div or any other wrapped element
      <div style={styles.innerDiv}></div>
    );
  }
});

However, as Facebooks favours the use of ES2015 (_"Our eventual goal is for ES6 classes to replace React.createClass completely"_, https://facebook.github.io/react/blog/2015/03/10/react-v0.13.html), here comes the corresponding code.

ES2015:

import React from 'react';

class NestedComponent extends React.Component {
  render() {
    const {listItem} = this.context.muiTheme;
    const {nestedLevel} = this.props;
    const styles = {
      innerDiv: {
          marginLeft: nestedLevel * listItem.nestedLevelDepth,
      },
    };
    return (
      // Indent the inner div or any other wrapped element
      <div style={styles.innerDiv}></div>
    );
  }
}
NestedComponent.propTypes = {
  // Define the nestedLevel property
  nestedLevel: React.PropTypes.number,
};
NestedComponent.defaultProps = {
    // Set the default value for the nestedLevel property
    nestedLevel: 0,
}; 
NestedComponent.contextTypes = {
  // Define the requirement for a muiTheme object in the context
  muiTheme: React.PropTypes.object.isRequried,
};

It's now users responsibility to set the text indent CSS property on the v1-beta branch.

@oliviertassinari Could you provide a complete example of a nested list in v1-beta?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zabojad picture zabojad  路  3Comments

ghost picture ghost  路  3Comments

revskill10 picture revskill10  路  3Comments

reflog picture reflog  路  3Comments

activatedgeek picture activatedgeek  路  3Comments