Blueprint: How to use Hotkeys in functional components?

Created on 29 Apr 2019  路  6Comments  路  Source: palantir/blueprint

export function App() {
    const [isDark, setIsDark] = React.useState(true);

    const switchTheme = () => {
        console.log(111);

        setIsDark(!isDark);
    };

    const getClass = () => {
        return classNames('menu', {
            'dark-theme': isDark,
            'light-theme': !isDark
        });
    };

    const renderHotkeys = () => {
        return (
            <Hotkeys>
                <Hotkey label="Switch Theme" combo="ctrl+shift+space" global={true} onKeyDown={switchTheme} />
            </Hotkeys>
        );
    };

    return (
        <div className={getClass()}>
            <Button className="switch-view-btn" icon={IconNames.MENU} onClick={switchView} intent={Intent.SUCCESS} />
            <UserBlock isFull={isFull} />
            <Menu className="a2-menu-ul" items={items} navigationItems={navItems} isFull={isFull} />
        </div>
    );
}

HotkeysTarget(App as any);

ReactDOM.render(<App />, document.getElementById('lmenu-holder'));

I have to cast App to any, because

Argument of type '() => Element' is not assignable to parameter of type 'IConstructor'.
Type '() => Element' provides no match for the signature 'new (...args: any[]): IHotkeysTargetComponent'

And in my console i see message

[Blueprint] @HotkeysTarget-decorated class should implement renderHotkeys.

P2 core feature request help wanted

Most helpful comment

Are there plans to support this use case? React offers a new technology hooks, but they can not be used in classes.

All 6 comments

This is unsupported. As you can see in the documentation it is clearly expecting a class component. const renderHotkeys has no binding to the App symbol there. Use a class.

Are there plans to support this use case? React offers a new technology hooks, but they can not be used in classes.

Same, I would love to use blueprint hotkeys but I am now fully committed to using hooks. I was able to sort of hack around this by doing the following:

@HotkeysTarget
export class AssortmentSplit_ProductTable extends React.Component<MyProps & {store: AssortmentSplitTableStore}> {
  render() {
    const {props: {...props}} = this
    return <AssortmentSplit_ProductTable_fn {...props} />
  }

  renderHotkeys() {
    // I tried getting to this via our content provider but it breaks hotkeys.  
    const {props: {store: {toggleShowSelectedProduct}}} = this
    return <Hotkeys>
      <Hotkey label={'Show Details'} combo={'d'} global preventDefault onKeyDown={toggleShowSelectedProduct} />
    </Hotkeys>
  }
}

This is super hacky though as I now have to pass in my store as trying to get it via the context breaks renderHotkeys and I can't get to it via my normal hook as I have to be in a class component.

Hooks are _awesome_ and I sincerely hope you guys provide a useHotkeys one in the not too distant future.

Having the same issue. Have my entire project using React hooks and unable to use this...

FWIW, +1 on supporting Hooks. I'm glad I found about this now with my first component (a Tree) and not after I'd already implemented a bunch of other ones. I can use classes even though it means I'll have a mixed codebase (blegh), so it's not the end of the world, but...

Untested, unfortunately HotkeyEvents/HotkeyScope is not exported or we could probably try this.

import { useCallback, useEffect, useMemo, KeyboardEvent } from "react";

function useHotkeys(hotkeys) {
    const localHotkeysEvents = useMemo(() => new HotkeysEvents(HotkeyScope.LOCAL), []);
    const globalHotkeysEvents = useMemo(() => new HotkeysEvents(HotkeyScope.GLOBAL), []);

    useEffect(() => {
            document.addEventListener("keydown", globalHotkeysEvents.handleKeyDown);
            document.addEventListener("keyup", globalHotkeysEvents.handleKeyUp);
            () => {
                document.removeEventListener("keydown", globalHotkeysEvents.handleKeyDown);
                document.removeEventListener("keyup", globalHotkeysEvents.handleKeyUp);

                globalHotkeysEvents.clear();
                localHotkeysEvents.clear();
            }
    }, []);

    localHotkeysEvents.setHotkeys(hotkeys.props);
    globalHotkeysEvents.setHotkeys(hotkeys.props);

    const tabIndex = hotkeys.props.tabIndex === undefined ? 0 : hotkeys.props.tabIndex;

    const onKeyDown = useCallback((e: KeyboardEvent<HTMLElement>) => {
        localHotkeysEvents.handleKeyDown(e.nativeEvent as KeyboardEvent);
    }, [localHotkeysEvents]);

    const onKeyUp = useCallback((e: KeyboardEvent<HTMLElement>) => {
        localHotkeysEvents.handleKeyUp(e.nativeEvent as KeyboardEvent);
    }, [localHotkeysEvents]);
    return { tabIndex, onKeyDown, onKeyUp };
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  路  3Comments

ernestofreyreg picture ernestofreyreg  路  3Comments

tgreenwatts picture tgreenwatts  路  3Comments

tomzaku picture tomzaku  路  3Comments

westrem picture westrem  路  3Comments