Created
August 29, 2019 22:58
-
-
Save lvl99/3893b8b8f475964c2f5cb6144a41ab0c to your computer and use it in GitHub Desktop.
useCallbackRef - React hook for triggering changes after ref has been received from DOM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useRef } from "react"; | |
import { render, cleanup } from "react-testing-library"; | |
import useCallbackRef from "./useCallbackRef"; | |
function TestInputRef<T = HTMLInputElement>({ | |
id = "test", | |
onSet, | |
onChange | |
}: { | |
id?: string; | |
onSet?: (newInput: T | null) => void; | |
onChange?: (newInput?: T | null, prevInput?: T | null) => void; | |
}) { | |
const [count, setCount] = useState(0); | |
const hasRef = useRef(false); | |
const [ref, setRef] = useCallbackRef<T>((newInput, prevInput) => { | |
setCount(count + 1); | |
if (newInput && !prevInput) { | |
hasRef.current = newInput === ref.current; | |
if (onSet) { | |
onSet(newInput); | |
} | |
} else { | |
if (onChange) { | |
onChange(newInput, prevInput); | |
} | |
} | |
}); | |
return ( | |
<div data-testid={id}> | |
<input data-testid="ref-input" ref={setRef} type="hidden" /> | |
<span data-testid="has-ref">{String(hasRef.current)}</span> | |
<span data-testid="count">{count}</span> | |
</div> | |
); | |
} | |
beforeEach(cleanup); | |
it("should assign the ref using the callbackRef", () => { | |
const check = { | |
inputRef: null | |
}; | |
const onSet = jest.fn(newInput => { | |
check.inputRef = newInput; | |
}); | |
const onChange = jest.fn((newInput, prevInput) => {}); | |
const { getByTestId } = render( | |
<TestInputRef onSet={onSet} onChange={onChange} /> | |
); | |
expect(onSet).toHaveBeenCalled(); | |
expect(check.inputRef).toBeInstanceOf(HTMLInputElement); | |
expect(onSet).toHaveBeenCalledWith(check.inputRef); | |
expect(onChange).not.toHaveBeenCalled(); | |
expect(getByTestId("has-ref")).toHaveTextContent("true"); | |
expect(getByTestId("count")).toHaveTextContent("1"); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This allows using the hook pattern of a "callback ref". | |
* | |
* Use this when you want to do side-effects after obtaining | |
* a ref from somewhere in the DOM because `useEffect` is not | |
* ideal in that situation. | |
* | |
* See: https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780 | |
* | |
* ``` | |
* function MyComponent() { | |
* const [ref, setRef] = useCallbackRef((newInput, prevInput) => { | |
* if (newInput && !prevInput) { | |
* // This is where you can put your effects after | |
* // receiving the input for the first time. | |
* } else { | |
* // If the input ever changes you can clean | |
* // up things related to the previous input here. | |
* } | |
* }); | |
* | |
* useEffect(() => { | |
* return () => { | |
* // If you need to clean-up on unmount do it here. | |
* // The following assumes your ref has a method of `destroy()`: | |
* ref.current.destroy(); | |
* } | |
* }); | |
* | |
* return <div ref={setRef} /> | |
* } | |
* ``` | |
*/ | |
import { useRef, useCallback } from "react"; | |
export type OnInput<T = any> = ( | |
newInput?: T | null, | |
prevInput?: T | null | |
) => void; | |
export default function useCallbackRef<T = any>( | |
cb: OnInput<T> | |
): [React.RefObject<T | null>, React.Ref<T>] { | |
const ref = useRef<T | null>(null); | |
const setRef = useCallback< | |
(newInput: T | null) => React.MutableRefObject<T | null> | |
>(newInput => { | |
if (ref.current !== newInput) { | |
const prevInput = ref.current; | |
ref.current = newInput; | |
if (!!cb) { | |
cb(newInput, prevInput); | |
} | |
} | |
return ref; | |
}, []); | |
return [ref, setRef]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment