Skip to content

Commit

Permalink
refactor: use captured input
Browse files Browse the repository at this point in the history
  • Loading branch information
guanbinrui committed Feb 28, 2020
1 parent f06ce44 commit e2e73bd
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 35 deletions.
8 changes: 4 additions & 4 deletions src/components/InjectedComponents/CommentBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ''
}
}),
)
Expand Down
14 changes: 9 additions & 5 deletions src/utils/__tests__/hooks/useCaptureEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement> = createRef<HTMLDivElement>()
Expand All @@ -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([''])
Expand All @@ -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 }))
Expand All @@ -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)

Expand Down
57 changes: 31 additions & 26 deletions src/utils/hooks/useCapturedEvents.ts
Original file line number Diff line number Diff line change
@@ -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)[] = [
Expand All @@ -19,37 +18,43 @@ export const captureEevnts: (keyof HTMLElementEventMap)[] = [
'change',
]

function binder<T extends keyof HTMLElementEventMap>(
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<HTMLInputElement | undefined | null>,
) {
const ref = or(_ref, React.useRef(null))
export function useCapturedInput(onChange: (newVal: string) => void, deps: any[] = []) {
const [node, setNode] = React.useState<HTMLInputElement | null>(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<T extends keyof HTMLElementEventMap>(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 [
<T extends keyof HTMLElementEventMap>(keys: T[], fn: (e: HTMLElementEventMap[T]) => void) =>
binder(node, keys, fn),
ref,
node,
] as const
}

0 comments on commit e2e73bd

Please sign in to comment.