Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using React how do I toggle the visibility of a nested component from a container component?

Goal

I am trying to mimic a List Control Component in React and Redux based on the Google Material Design layout.

The list control will allow you to create, rename and delete items in the list without navigating to a new page.

The actions for renaming and deleting a list item will be shown in a popup menu component triggered by clicking a link on each list item.

Current Component Structure

I have a top level Container component ListItemPage linked to Redux i am using to maintain the state of the list before i push the changes into the store.

In ListItemPage i am rendering a list of items and each item has a unique Menu Component

ListItemPage Component

class ListItemPage extends Component {
    constructor(props, context) {
        super(props, context);
    }

    onMenuClick(){}
    onRename(){}
    onDelete(){}

    render () {
        const { listItems } = this.props;

        return(
            <div>
                <ul>
                    {
                        listItems.map(listItem =>
                            <li>
                                <a href={`/items/${listItem.id}`}>
                                    {listItem.title}
                                </a>
                                <a href="#" onClick={onMenuClick}>
                                    <i className="material-icons">more_vert</i>
                                </a>
                                <Menu
                                    showMenu={showMenu}
                                    onRename={onRename}
                                    onDelete={onDelete}/>
                            </li>
                        )
                    }
                <ul>
                <a href="#" onClick={null}>
                    <i className="material-icons">add</i>
                </a>
            </div>
        );
    }
}

function mapStateToProps(state, ownProps){
    return {
        listItems: state.listItems
    }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(listItemActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(ListItemPage);

Menu Component

const Menu = (showMenu, onDelete, onRename) => {
    if(showMenu) {
        return(
            <nav>
                <a href="#" onDelete={onDelete}>Delete</a>
                <a href="#" onRename={onRename}>Rename</a>
            </nav>
        );
    }

    return null;
}

export default Menu;

Redux Store Data Structure

let listItems = [
    { id: 1, title: 'Item A' },
    { id: 2, title: 'Item B' },
    { id: 3, title: 'Item C' }
];

Problem

I am unable to figure out how i show and hide the unique menu componenent based on the list item onClickEvent.

I was thinking of some how passing in a bool for that unqiue item of showMenu but i am unsure where to define this for each item, or even if this is the best way to do this.

I know i can use Refs to achieve this but i would prefer not to given that it is advised to only use Refs as a last resort.

like image 228
Matthew Coleman Avatar asked Dec 09 '16 14:12

Matthew Coleman


1 Answers

Great first post! Good call on limiting use of refs too.

One approach could be to keep some state in your ListItemPage component of which menu should be currently displayed.

constructor(props, context) {
    super(props, context);
    this.state = { showMenu: null };
}

Then, in your onMenuClick function, receive the id of the item to show the menu for, and assign to state.

onMenuClick(id) {
  this.setState({ showMenu: id });
}

This would be passed into your Menu as follows.

showMenu={this.state.showMenu === listItem.id}

Closing the menu would just need to reset the value of this.state.showMenu to null.

This is assuming there would only be one menu displayed at a given time. If that's not true, let me know and I'll update.

like image 187
BradByte Avatar answered Sep 18 '22 17:09

BradByte