react-aria and Typescript building a popover component error with value passing

1.1k Views Asked by At

Morning, all.

I have an issue I have been struggling with for a little while. I have been using react-aria to build a popover in Storybook. This popover is built in two components, the first one is the popover itself, this works fine:

import { StyledPopover } from './PopOver.styles';
import {
    DismissButton,
    FocusScope,
    mergeProps,
    useDialog,
    useModal,
    useOverlay,
} from 'react-aria';
import React, { forwardRef } from 'react';
import { useObjectRef } from '@react-aria/utils';

export interface PopoverProps {
    title: string;
    children: React.ReactNode;
    isOpen: boolean;
    onClose: () => void;
}

const Popover = React.forwardRef<HTMLDivElement, PopoverProps>(
    ({ title, children, isOpen, onClose }, ref) => {
        const forwardRef = useObjectRef(ref);

        // Handle interacting outside the dialog and pressing
        // the Escape key to close the modal.
        const { overlayProps } = useOverlay(
            {
                onClose,
                isOpen,
                isDismissable: true,
            },
            forwardRef
        );

        // Hide content outside the modal from screen readers.
        const { modalProps } = useModal();

        // Get props for the dialog and its title
        const { dialogProps, titleProps } = useDialog({}, forwardRef);

        return (
            <FocusScope restoreFocus>
                <StyledPopover
                    {...mergeProps(overlayProps, dialogProps, modalProps)}
                    ref={ref}
                >
                    <h3 {...titleProps} style={{ marginTop: 0 }}>
                        {title}
                    </h3>
                    {children}

                    <DismissButton onDismiss={onClose} />
                </StyledPopover>
            </FocusScope>
        );
    }
);

export { Popover };

Then I have the the button and the state:

import React from 'react';
import {
    OverlayContainer,
    useOverlayPosition,
    useOverlayTrigger,
} from 'react-aria';
import { Button } from '../Button';
import { useOverlayTriggerState } from 'react-stately';
import { Popover } from 'Atoms/Popover/Popover';

export interface PopoverButtonProps {
    title: string;
    buttonText: string;
    children: React.ReactNode;
    disabled: boolean;
}

const PopoverButton = ({
    buttonText,
    title,
    children,
    disabled,
}: PopoverButtonProps) => {
    const state: any = useOverlayTriggerState({});

    const triggerRef = React.useRef<HTMLButtonElement>(null);
    const overlayRef: any = React.useRef<HTMLDivElement>(null);

    // Get props for the trigger and overlay. This also handles
    // hiding the overlay when a parent element of the trigger scrolls
    // (which invalidates the popover positioning).
    const { triggerProps, overlayProps } = useOverlayTrigger(
        { type: 'dialog' },
        state,
        triggerRef
    );

    // Get popover positioning props relative to the trigger
    const { overlayProps: positionProps } = useOverlayPosition({
        targetRef: triggerRef,
        overlayRef,
        placement: 'top',
        offset: 5,
        isOpen: state.isOpen,
    });
    //
    // const handleOnClick = (e: any) => {
    //  console.log(triggerProps);
    //  triggerProps.onPress && triggerProps.onPress(e);
    // };
    console.log(triggerProps);
    return (
        <>
            <Button
                onClick={(e: React.MouseEvent<HTMLInputElement>) =>
                    triggerProps.onPress && triggerProps.onPress(e)
                }
                style="secondary"
                size="small"
                disabled={disabled}
            >
                {buttonText}
            </Button>
            {state.isOpen && (
                <OverlayContainer>
                    <Popover
                        {...overlayProps}
                        {...positionProps}
                        ref={overlayRef}
                        title={title}
                        isOpen={state.isOpen}
                        onClose={state.close}
                    >
                        {children}
                    </Popover>
                </OverlayContainer>
            )}
        </>
    );
};

export { PopoverButton };

Now, react-aria useButton takes an onPress, not an onClick.

So, in my Button component I have casted the onClick like this:

import classNames from 'classnames';
import { forwardRef } from 'react';
import { StyledButton } from './Button.styles';
import { useButton } from 'react-aria';
import { useObjectRef } from '@react-aria/utils';
import React from 'react';

export interface ButtonProps
    extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'style'> {
    children: React.ReactNode;
    type?: 'submit' | 'button' | 'reset';
    style?: 'primary' | 'secondary' | 'icon' | 'text';
    size?: 'large' | 'medium' | 'small';
    block?: boolean;
    disabled?: boolean;

    testId?: string;
    onPress?: () => void;
}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
    (
        {
            children,
            type,
            style = 'primary',
            size = 'large',
            block = false,
            disabled = false,
            testId,

            onPress,
            ...props
        },
        ref
    ) => {
        const classes = classNames(style, `btn-${size}`, {
            block,
        });
        const objRef = useObjectRef(ref);
        const { buttonProps } = useButton({ onPress }, objRef);

        return (
            <StyledButton
                {...buttonProps}
                className={classes}
                onClick={onPress}
                type={type}
                disabled={disabled}
                data-testid={testId}
                ref={ref}
                {...props}
            >
                {children}
            </StyledButton>
        );
    }
);

export { Button };

In my popoverButton component, I am passing (e) as it is required for react-aria:

onClick={(e: React.MouseEvent<HTMLInputElement>) =>
                    triggerProps.onPress && triggerProps.onPress(e)
                }

However, I am getting these two errors -

on (e) -

Argument of type 'MouseEventHandler<HTMLButtonElement>' is not assignable to parameter of type 'PressEvent'.
  Type 'MouseEventHandler<HTMLButtonElement>' is missing the following properties from type 'PressEvent': type, pointerType, target, shiftKey, and 3 more.ts(2345)

onClick -

Type '(e: React.MouseEventHandler<HTMLButtonElement>) => void | undefined' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.
  Types of parameters 'e' and 'event' are incompatible.
    Type 'MouseEvent<HTMLButtonElement, MouseEvent>' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.
      Type 'MouseEvent<HTMLButtonElement, MouseEvent>' provides no match for the signature '(event: MouseEvent<HTMLButtonElement, MouseEvent>): void'.ts(2322)
index.d.ts(1446, 9): The expected type comes from property 'onClick' which is declared here on type 'IntrinsicAttributes & ButtonProps & RefAttributes<HTMLButtonElement>'

Now, the button works and the popup does appear, however it only disappears when clicking the button when it should dismiss when clicking anywhere on the screen. I think this is down to the issues I currently have with the onClick and onPress?

Any ideas?

0

There are 0 best solutions below