From 82826cb56f2f74547fcf02819f02c64d025c62b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Thu, 20 May 2021 16:06:04 +0300 Subject: [PATCH 1/2] Compose: useMergeRefs: add test for disabling refs --- .../src/hooks/use-merge-refs/test/index.js | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/packages/compose/src/hooks/use-merge-refs/test/index.js b/packages/compose/src/hooks/use-merge-refs/test/index.js index d9b6fda25d3360..6893148462583a 100644 --- a/packages/compose/src/hooks/use-merge-refs/test/index.js +++ b/packages/compose/src/hooks/use-merge-refs/test/index.js @@ -36,7 +36,12 @@ describe( 'useMergeRefs', () => { renderCallback.history = []; - function MergedRefs( { count, tagName: TagName = 'div' } ) { + function MergedRefs( { + count, + tagName: TagName = 'div', + disable1, + disable2, + } ) { function refCallback1( value ) { refCallback1.history.push( value ); } @@ -51,14 +56,14 @@ describe( 'useMergeRefs', () => { renderCallback( [ refCallback1.history, refCallback2.history ] ); - return ( - - ); + const ref1 = useCallback( refCallback1, [] ); + const ref2 = useCallback( refCallback2, [ count ] ); + const mergedRefs = useMergeRefs( [ + ! disable1 && ref1, + ! disable2 && ref2, + ] ); + + return ; } beforeEach( () => { @@ -273,4 +278,53 @@ describe( 'useMergeRefs', () => { [ [], [ newElement, null ] ], ] ); } ); + + it( 'should allow disabling a ref', () => { + const rootElement = document.getElementById( 'root' ); + + ReactDOM.render( , rootElement ); + + const originalElement = rootElement.firstElementChild; + + // Render 1: ref 1 should be disabled. + expect( renderCallback.history ).toEqual( [ + [ [], [ originalElement ] ], + ] ); + + ReactDOM.render( , rootElement ); + + // Render 2: ref 1 should be enabled and receive the ref. Note that the + // callback hasn't changed, so the original callback function will be + // called. Ref 2 should be disabled, so called with null. + expect( renderCallback.history ).toEqual( [ + [ [ originalElement ], [ originalElement, null ] ], + [ [], [] ], + ] ); + + ReactDOM.render( , rootElement ); + + // Render 3: ref 1 should again be disabled. Ref 2 to should receive a + // ref with the new callback function because the count has been + // changed. + expect( renderCallback.history ).toEqual( [ + [ + [ originalElement, null ], + [ originalElement, null ], + ], + [ [], [] ], + [ [], [ originalElement ] ], + ] ); + + ReactDOM.render( null, rootElement ); + + // Unmount: current callback functions should receive null. + expect( renderCallback.history ).toEqual( [ + [ + [ originalElement, null ], + [ originalElement, null ], + ], + [ [], [] ], + [ [], [ originalElement, null ] ], + ] ); + } ); } ); From a45fbcd0bee02a3123cb81ac38fc06c55d01d440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Thu, 20 May 2021 17:39:29 +0300 Subject: [PATCH 2/2] Better documentation --- packages/compose/README.md | 43 ++++++++++++++++--- .../compose/src/hooks/use-merge-refs/index.js | 43 ++++++++++++++++--- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/packages/compose/README.md b/packages/compose/README.md index 016560af43f142..8294f3b2215b06 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -305,13 +305,42 @@ _Returns_ # **useMergeRefs** -Merges refs into one ref callback. Ensures the merged ref callbacks are only -called when it changes (as a result of a `useCallback` dependency update) or -when the ref value changes. If you don't wish a ref callback to be called on -every render, wrap it with `useCallback( ref, [] )`. -Dependencies can be added, but when a dependency changes, the old ref -callback will be called with `null` and the new ref callback will be called -with the same node. +Merges refs into one ref callback. + +It also ensures that the merged ref callbacks are only called when they +change (as a result of a `useCallback` dependency update) OR when the ref +value changes, just as React does when passing a single ref callback to the +component. + +As expected, if you pass a new function on every render, the ref callback +will be called after every render. + +If you don't wish a ref callback to be called after every render, wrap it +with `useCallback( callback, dependencies )`. When a dependency changes, the +old ref callback will be called with `null` and the new ref callback will be +called with the same value. + +To make ref callbacks easier to use, you can also pass the result of +`useRefEffect`, which makes cleanup easier by allowing you to return a +cleanup function instead of handling `null`. + +It's also possible to _disable_ a ref (and its behaviour) by simply not +passing the ref. + +```jsx +const ref = useRefEffect( ( node ) => { + node.addEventListener( ... ); + return () => { + node.removeEventListener( ... ); + }; +}, [ ...dependencies ] ); +const otherRef = useRef(); +const mergedRefs useMergeRefs( [ + enabled && ref, + otherRef, +] ); +return
; +``` _Parameters_ diff --git a/packages/compose/src/hooks/use-merge-refs/index.js b/packages/compose/src/hooks/use-merge-refs/index.js index bd12bd32406e8b..d9bd7e6ce4e1e8 100644 --- a/packages/compose/src/hooks/use-merge-refs/index.js +++ b/packages/compose/src/hooks/use-merge-refs/index.js @@ -15,13 +15,42 @@ function assignRef( ref, value ) { } /** - * Merges refs into one ref callback. Ensures the merged ref callbacks are only - * called when it changes (as a result of a `useCallback` dependency update) or - * when the ref value changes. If you don't wish a ref callback to be called on - * every render, wrap it with `useCallback( ref, [] )`. - * Dependencies can be added, but when a dependency changes, the old ref - * callback will be called with `null` and the new ref callback will be called - * with the same node. + * Merges refs into one ref callback. + * + * It also ensures that the merged ref callbacks are only called when they + * change (as a result of a `useCallback` dependency update) OR when the ref + * value changes, just as React does when passing a single ref callback to the + * component. + * + * As expected, if you pass a new function on every render, the ref callback + * will be called after every render. + * + * If you don't wish a ref callback to be called after every render, wrap it + * with `useCallback( callback, dependencies )`. When a dependency changes, the + * old ref callback will be called with `null` and the new ref callback will be + * called with the same value. + * + * To make ref callbacks easier to use, you can also pass the result of + * `useRefEffect`, which makes cleanup easier by allowing you to return a + * cleanup function instead of handling `null`. + * + * It's also possible to _disable_ a ref (and its behaviour) by simply not + * passing the ref. + * + * ```jsx + * const ref = useRefEffect( ( node ) => { + * node.addEventListener( ... ); + * return () => { + * node.removeEventListener( ... ); + * }; + * }, [ ...dependencies ] ); + * const otherRef = useRef(); + * const mergedRefs useMergeRefs( [ + * enabled && ref, + * otherRef, + * ] ); + * return
; + * ``` * * @param {Array} refs The refs to be merged. *