Я хотел бы добавить события отслеживания аналитики ко всем кнопкам в моем приложении. Все кнопки mui
кнопки, но я чувствую, что моя проблема была бы такой же, если бы я просто использовал собственный HTML button
.
Моя идея состоит в том, чтобы создать компонент-оболочку, которым я заменяю все компоненты Button
, который в основном обертывает компонент Button
, добавляет некоторую дополнительную логику к его слушателям событий, а затем продолжает распространять любые подключенные слушатели.
Я дошел до того, что не знаю, как лучше всего это сделать, а также пересылать ref
s. Вопрос может заключаться в том, «как мне одновременно пересылать ссылки и использовать локальные ссылки createRef
, хотя я не уверен.
Компонент выглядит так:
CustomButton.tsx
import _ from 'lodash';
import { Button, ButtonProps } from '@mui/material';
import { analyticsTrack, mergeRefs } from 'utils/functions';
import { forwardRef, ForwardedRef, createRef } from 'react';
interface CustomButtonProps extends ButtonProps {
trackingName?: string;
}
const CustomButton = forwardRef(
(
{ trackingName, children, ...rest }: CustomButtonProps,
ref: ForwardedRef<any>
) => {
const buttonRef = createRef();
function handleClick(...args: any) {
analyticsTrack('Button Clicked', {
props: rest,
ariaLabel: rest['aria-label'],
trackingName,
buttonText: typeof children === 'string' ? children : '',
});
if (rest.onClick) {
rest.onClick(args);
}
}
return (
// @ts-ignore
// eslint-disable-next-line react/jsx-no-bind
<Button
{..._.omitBy(rest, ['onClick'])}
onMouseDown = {(event) => event.stopPropagation()}
onTouchStart = {(event) => event.stopPropagation()}
onClick = {(event) => {
event.stopPropagation();
event.preventDefault();
handleClick();
// @ts-ignore
buttonRef.current && buttonRef.current.dispatchEvent(event);
}}
// @ts-ignore
ref = {mergeRefs(ref, buttonRef)}
>
{children}
</Button>
);
}
);
export default CustomButton;
Я также очень запутался в типах, не совсем понимаю, почему существует разница между типами ForwardRef
, Ref.
и RefObject
. Функция mergeRefs
выглядит следующим образом:
export const mergeRefs = (...refs: RefObject<Element | null>[]) => {
return ( node: Element ) => {
for (const ref of refs) {
// @ts-ignore
ref && !ref.current ? ref.current = node : null;
}
}
}
Я прочитал страницу forwardRefs
в документации по реакции, что заставило меня поверить, что это путь, по которому я должен пойти в целом, чтобы добавить обработку событий отслеживания ко всем моим компонентам кнопок в приложении. Я также читал различные ответы о переполнении стека при остановке, а затем повторном выпуске собственных событий, таких как submit
(которые, например, используются различными частями приложения для захвата Formik, поэтому, почему простая обработка кликов сама по себе не является достаточный). Эти три ответа помогли мне оказаться там, где я сейчас:
Как я могу вызвать React useRef условно в обертке Portal и иметь лучший код?
Как использовать рефы в React с Typescript
Компонент React высшего порядка для отслеживания кликов
На данный момент, однако, я получаю сообщение об ошибке о том, что не могу вызвать dispatchEvent
для объекта event
, поскольку, по-видимому, типы SyntheticEvent
не могут быть переданы dispatchEvent
, и я обеспокоен тем, что ошибаюсь на этом пути. здесь.
Короче говоря, каков правильный или, возможно, рекомендованный реакцией способ «невидимой» упаковки компонента, работы с его функциями и обработчиками событий, а затем продолжения вызовов всех указанных функций и обработчиков событий без их остановки? В данном случае с конкретным вариантом использования библиотеки отслеживания.
Я искал в документации по реакции, а также гуглил различные руководства и просматривал кодовые базы различных библиотек отслеживания и интеграций реагирования (таких как Segment и Google Analytics), но не смог найти примеры в этом направлении, что заставляет меня беспокоиться о том, что я м на непроторенном и потому неверном пути и имея принципиальное непонимание.
Не должно быть необходимости обрабатывать внутренний ref
для вашего варианта использования, просто перенаправить его.
Просто не забудьте обернуть обработчик события onClick
своей собственной реализацией, чтобы сначала вызвать функцию analyticsTrack
:
const CustomButton = forwardRef<any, CustomButtonProps>(
({ trackingName, children, onClick, ...rest }, ref) => {
function handleClick(event: any) {
analyticsTrack("Button Clicked", {
props: rest,
ariaLabel: rest["aria-label"],
trackingName,
buttonText: typeof children === "string" ? children : ""
});
if (onClick) {
onClick(event);
}
}
return (
<Button {...rest} onClick = {handleClick} ref = {ref}>
{children}
</Button>
);
}
);
Демо: https://codesandbox.io/s/nameless-frog-jtdves?file=/src/App.tsx
Если да, то по какой причине вы stopPropagation()
?
Это... отличный вопрос... Я не помню, и его удаление действительно решает мою проблему, лол. Технически это выполняет то, что я искал, но мне все еще очень интересно, как я могу получить доступ к элементу Button, чтобы я мог продолжить распространение событий от него или сделать что-то еще.
Если вам нужно управлять внутренним ref
и в то же время выставлять его (но потребляющий компонент может его не предоставлять), сделайте это, например, как описано в stackoverflow.com/questions/73724784/…. При этом будьте осторожны при отправке вашего события, чтобы не создавать бесконечный цикл...
достаточно хорошо для меня, технически этот комментарий является ответом
Это уже работает, моя забота — захватывать и повторять такие события, как
submit