Search code examples
office-ui-fabricreact-tsxoffice-ui-fabric-reactfluent-ui

Context Menu not reflecting check/uncheck on click


I have a context menu on click of a button. There are 3 items which are initially checked. On click of each item, I am toggling the check. The check/uncheck is not reflecting while the context menu is open. This used to work earlier but with the latest react versions it looks to be broken.

The snippet is here

import { initializeIcons } from '@uifabric/icons';
import { DefaultButton, IContextualMenuItem, IContextualMenuProps, IContextualMenuStyles } from 'office-ui-fabric-react';
import React from 'react';
initializeIcons();

//Without this style defined, check/uncheck will not reflect in UI.
const cmStyles:Partial<IContextualMenuStyles> = {
    title:{},
    header:{},
    list:{
    },
    root:{
    },
    subComponentStyles:{
        callout:{
            root:{
            }
        },
        menuItem:{}
    },
    container:{
    }
}

const keys: string[] = [
    'Item1',
    'Item2',
    'Item3'
];

interface IState {
    updateUI: boolean;
}

export default class ContextMenuCheck extends React.Component<{}, IState>{
    state:IState = {
        updateUI: false
    }

    constructor(props:any){
        super(props);
    }

    onTogglePlanType = (ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, item?: IContextualMenuItem):void => {
        ev && ev.preventDefault();//This will not close the menu
        item.checked = !item.checked;
        this.setState({updateUI:true});
    };

    menuItems: IContextualMenuItem[] = [
        {
          key: keys[0],
          text: keys[0],
          canCheck: true,
          checked: true,
          onClick: this.onTogglePlanType
        },
        {
          key: keys[1],
          text: keys[1],
          canCheck: true,
          checked: true,
          onClick: this.onTogglePlanType
        },
        {
          key: keys[2],
          text: keys[2],
          canCheck: true,
          checked: true,
          onClick: this.onTogglePlanType
        }
    ];

    menuProps: IContextualMenuProps = {
        items:this.menuItems,
        styles: cmStyles
    };

    componentDidMount() {
    }

    render() {
        return (
        <DefaultButton text="Click Me"  menuProps={this.menuProps}/>
        );
    }
}

ReactDOM.render(
  <ContextMenuCheck />, 
  document.getElementById("content")
);


Solution

  • I put together a working example here.

    Your sample is close, just had a few missing pieces:

    • Since menuItems is defined with checked hard-coded to true, the Context Menu will never render those without a checkmark.
    • In my example, I put the menu items checked state into the overall state, so that onTogglePlanType has the ability to set checked (or not checked) for each individual menu item.
    • Finally, menuItems must be rebuilt on each render so that it is able to take checked into account in building the menu.

    I changed IState to just hold checked values:

    type itemChecked = { [key: string]: boolean };
    
    interface IState {
        checkedMenuItems: itemChecked;  
    }
    

    Then I changed the callback to explicitly set values for each menu item:

    onTogglePlanType = (ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, item?: IContextualMenuItem):void => {
            ev && ev.preventDefault();
            // Use ... syntax to make a copy of the object
            const itemChecked = { ...this.state.itemChecked }; 
            itemChecked[item.key] = !itemChecked[item.key];
    
            this.setState({ itemChecked: itemChecked });
        };
    

    Then, by moving the definition of menuItems into the render() function, it works as expected.