0

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")
);

Putta
  • 67
  • 7

1 Answers1

1

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.

JD Huntington
  • 348
  • 1
  • 5
  • Hi JD Huntington, Thanks for your reply. You have mentioned that the update won't happen if the items are the exact same array. Is this something which got changed recently? I have a vscode sample where the exact code works. i.e the check/uncheck does get updated without recreating the array. The difference is the package.json which is a few months old. Thanks – Putta Jun 10 '20 at 05:41
  • Afraid I don't know the history of this particular feature. It may have been changed recently. That said, making the props reflect what the component should look like is the best way to ensure this type of menu works correctly and is futureproof. There were some bugs in previous versions of Fabric that permitted React anti-patterns to work, but these are being cleaned up over time. – JD Huntington Jun 10 '20 at 20:37