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:

    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.