From 9f197ed4259af00f09d7dccd7673db43ceee4479 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Fri, 28 Feb 2020 18:23:55 +0800 Subject: [PATCH] refactor: use captured input --- .../InjectedComponents/CommentBox.tsx | 8 +-- .../__tests__/hooks/useCaptureEvents.tsx | 14 +++-- src/utils/hooks/useCapturedEvents.ts | 57 ++++++++++--------- 3 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/components/InjectedComponents/CommentBox.tsx b/src/components/InjectedComponents/CommentBox.tsx index ecea46936593..5bbd60c6995a 100644 --- a/src/components/InjectedComponents/CommentBox.tsx +++ b/src/components/InjectedComponents/CommentBox.tsx @@ -36,14 +36,14 @@ export interface CommentBoxProps { } export function CommentBox(props: CommentBoxProps) { const classes = useStyles() - const [binder, inputRef] = useCapturedInput(() => {}) + const [binder, inputRef, node] = useCapturedInput(() => {}) const { t } = useI18N() useEffect( binder(['keypress'], e => { - if (!inputRef.current) return + if (!node) return if (e.key === 'Enter') { - props.onSubmit(inputRef.current.value) - inputRef.current.value = '' + props.onSubmit(node.value) + node.value = '' } }), ) diff --git a/src/utils/__tests__/hooks/useCaptureEvents.tsx b/src/utils/__tests__/hooks/useCaptureEvents.tsx index a209267acb12..4abc8fcee597 100644 --- a/src/utils/__tests__/hooks/useCaptureEvents.tsx +++ b/src/utils/__tests__/hooks/useCaptureEvents.tsx @@ -4,7 +4,7 @@ import Adapter from 'enzyme-adapter-react-16' configure({ adapter: new Adapter() }) import React, { createRef, RefObject } from 'react' -import { renderHook } from '@testing-library/react-hooks' +import { renderHook, act } from '@testing-library/react-hooks' import { useCapturedInput, captureEevnts } from '../../hooks/useCapturedEvents' const containerRef: RefObject = createRef() @@ -20,7 +20,8 @@ beforeEach(() => { test('invoke callback with input value', () => { const inputSpy = jasmine.createSpy() - renderHook(() => useCapturedInput(inputSpy, [], inputRef)) + const [_, updateInputNode] = renderHook(() => useCapturedInput(inputSpy, [])).result.current + act(() => updateInputNode(inputRef.current)) inputRef.current!.dispatchEvent(new CustomEvent('input', { bubbles: true })) expect(inputSpy.calls.argsFor(0)).toStrictEqual(['']) @@ -42,7 +43,8 @@ for (const name of captureEevnts) { }) test(`capture event: ${name}`, () => { const containerSpy = jasmine.createSpy() - renderHook(() => useCapturedInput(() => {}, [], inputRef)) + const [_, updateInputNode] = renderHook(() => useCapturedInput(() => {}, [])).result.current + act(() => updateInputNode(inputRef.current)) containerRef.current!.addEventListener(name, containerSpy) inputRef.current!.dispatchEvent(new CustomEvent(name, { bubbles: true })) @@ -52,12 +54,14 @@ for (const name of captureEevnts) { }) test(`remove listener: ${name}`, () => { const containerSpy = jasmine.createSpy() - containerRef.current!.addEventListener(name, containerSpy) inputRef.current!.dispatchEvent(new CustomEvent(name, { bubbles: true })) expect(containerSpy.calls.count()).toBe(1) - const hook = renderHook(() => useCapturedInput(() => {}, [], inputRef)) + const hook = renderHook(() => useCapturedInput(() => {}, [])) + const [_, updateInputNode] = hook.result.current + act(() => updateInputNode(inputRef.current)) + inputRef.current!.dispatchEvent(new CustomEvent(name, { bubbles: true })) expect(containerSpy.calls.count()).toBe(1) diff --git a/src/utils/hooks/useCapturedEvents.ts b/src/utils/hooks/useCapturedEvents.ts index 09e5a952aa1d..bcf0566e5d27 100644 --- a/src/utils/hooks/useCapturedEvents.ts +++ b/src/utils/hooks/useCapturedEvents.ts @@ -1,6 +1,5 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { useEffect, useCallback } from 'react' -import { or } from '../../components/custom-ui-helper' import React from 'react' export const captureEevnts: (keyof HTMLElementEventMap)[] = [ @@ -19,37 +18,43 @@ export const captureEevnts: (keyof HTMLElementEventMap)[] = [ 'change', ] +function binder( + node: HTMLInputElement | null, + keys: T[], + fn: (e: HTMLElementEventMap[T]) => void, +) { + let current: HTMLInputElement | null + const bind = (input: HTMLInputElement) => keys.forEach(k => input.addEventListener(k, fn, true)) + const unbind = (input: HTMLInputElement) => keys.forEach(k => input.removeEventListener(k, fn, true)) + return () => { + if (!node) return + current = node + bind(current) + return () => { + if (!current) return + unbind(current) + current = null + } + } +} + /** * ! Call this hook inside Shadow Root! */ -export function useCapturedInput( - onChange: (newVal: string) => void, - deps: any[] = [], - _ref?: React.MutableRefObject, -) { - const ref = or(_ref, React.useRef(null)) +export function useCapturedInput(onChange: (newVal: string) => void, deps: any[] = []) { + const [node, setNode] = React.useState(null) + const ref = useCallback((nextNode: HTMLInputElement | null) => setNode(nextNode), []) const stop = useCallback((e: Event) => e.stopPropagation(), deps) const use = useCallback( (e: Event) => onChange((e.currentTarget as HTMLInputElement)?.value ?? (e.target as HTMLInputElement)?.value), [onChange].concat(deps), ) - function binder(keys: T[], fn: (e: HTMLElementEventMap[T]) => void) { - let current: HTMLInputElement | null - const bind = (input: HTMLInputElement) => keys.forEach(k => input.addEventListener(k, fn, true)) - const unbind = (input: HTMLInputElement) => keys.forEach(k => input.removeEventListener(k, fn, true)) - - return () => { - if (!ref.current) return - current = ref.current - bind(current) - return () => { - if (!current) return - unbind(current) - current = null - } - } - } - useEffect(binder(['input'], use), [ref.current].concat(deps)) - useEffect(binder(captureEevnts, stop), [ref.current].concat(deps)) - return [binder, ref] as const + useEffect(binder(node, ['input'], use), [node].concat(deps)) + useEffect(binder(node, captureEevnts, stop), [node].concat(deps)) + return [ + (keys: T[], fn: (e: HTMLElementEventMap[T]) => void) => + binder(node, keys, fn), + ref, + node, + ] as const }