Skip to content

Commit

Permalink
feat: add mergeRef
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Oct 8, 2019
1 parent 0f87879 commit 32278f9
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 31 deletions.
30 changes: 30 additions & 0 deletions README.md
Expand Up @@ -22,6 +22,7 @@ you may want to use ~~a callback ref instead~~ .... __useCallbackRef__ instead.
[Hooks API Reference](https://reactjs.org/docs/hooks-reference.html#useref)

# API
## useRef API
API is 99% compatible with React `createRef` and `useRef`, and just adds another argument - `callback`,
which would be called on __ref update__.

Expand All @@ -48,5 +49,34 @@ const Component = () => {
}
```

💡 You can use `useCallbackRef` to convert RefObject into RefCallback, creating bridges between the old and the new code
```js
// some old component
const onRefUpdate = (newValue) => {...}
const refObject = useCallbackRef(null, onRefUpdate);
// ...
<SomeNewComponent ref={refObject}/>
```

## Additional API
### mergeRefs
`mergeRefs(refs: arrayOfRefs, [defaultValue]):ReactMutableRef` - merges a few refs together

```js
import React from 'react'
import {mergeRefs} from 'use-callback-ref'

const MergedComponent = React.forwardRef(function Example(props, ref) {
const localRef = React.useRef()
return <div ref={mergeRefs([localRef, ref])} />
})
```

> based on https://github.com/smooth-code/react-merge-refs, just exposes RefObject, instead of callback
When developing low level UI components, it is common to have to use a local ref but also support an external one using React.forwardRef. Natively, React does not offer a way to set two refs inside the ref property. This is the goal of this small utility.



# License
MIT
93 changes: 65 additions & 28 deletions __tests__/index.tsx
@@ -1,37 +1,74 @@
import * as React from 'react';
import {createRef} from "react";
import {mount} from 'enzyme';
import {createCallbackRef, useCallbackRef} from "../src";
import {createCallbackRef, mergeRefs, useCallbackRef} from "../src";


describe('Specs', () => {
it('base - createRef', () => {
const ref = React.createRef<HTMLDivElement>();
mount(<div ref={ref}>test</div>);
expect(ref.current).not.toBe(null);
});
describe('useCallbackRef', () => {
it('base - createRef', () => {
const ref = React.createRef<HTMLDivElement>();
mount(<div ref={ref}>test</div>);
expect(ref.current).not.toBe(null);
});

it('shall work as createRef', () => {
const spy = jest.fn();
const ref = createCallbackRef<HTMLDivElement>(spy);
mount(<div ref={ref}>test</div>);
expect(spy).toBeCalledWith(ref.current, null);
expect(ref.current).not.toBe(null);
});

it('shall work as useRef', () => {
const spy = jest.fn();
let counter = 0;
const Test = () => {
const x = counter++;
const ref = useCallbackRef<HTMLDivElement>(null, () => spy(x));
return <div key={x < 2 ? '1' : '2'} ref={ref}>test</div>;
};
const wrapper = mount(<Test/>);
expect(spy).toBeCalledWith(0);

wrapper.setProps({x: 42});
expect(spy).toBeCalledWith(0);

it('shall work as createRef', () => {
const spy = jest.fn();
const ref = createCallbackRef<HTMLDivElement>(spy);
mount(<div ref={ref}>test</div>);
expect(spy).toBeCalledWith(ref.current, null);
expect(ref.current).not.toBe(null);
wrapper.setProps({x: 24});
expect(spy).toBeCalledWith(2);
});
});

it('shall work as useRef', () => {
const spy = jest.fn();
let counter = 0;
const Test = () => {
const x = counter++;
const ref = useCallbackRef<HTMLDivElement>(null, () => spy(x));
return <div key={x<2 ? '1' : '2'} ref={ref}>test</div>;
};
const wrapper = mount(<Test/>);
expect(spy).toBeCalledWith(0);

wrapper.setProps({x: 42});
expect(spy).toBeCalledWith(0);

wrapper.setProps({x: 24});
expect(spy).toBeCalledWith(2);
describe('mergeRef', () => {
it("merges two refs", () => {
const spy1 = jest.fn();
const ref1 = createCallbackRef<HTMLDivElement>(spy1);
const spy2 = jest.fn();
const ref2 = createCallbackRef<HTMLDivElement>(spy2);
const ref3 = createRef() as React.MutableRefObject<any>;
const ref4 = jest.fn();

const TestComponent = () => (
<div
ref={mergeRefs([
ref1,
ref2,
ref3,
ref4,
])}
>test</div>
);

mount(<TestComponent />);

const ref = ref1.current;
expect(ref).not.toBe(null);

expect(spy1).toBeCalledWith(ref, null);
expect(spy2).toBeCalledWith(ref, null);
expect(ref3.current).toBe(ref);
expect(ref4).toBeCalledWith(ref);
});
});

});
11 changes: 11 additions & 0 deletions package.json
Expand Up @@ -39,5 +39,16 @@
"hoot",
"useRef",
"createRef"
],
"size-limit": [
{
"path": "dist/es2015/index.js",
"limit": "0.5 KB"
},
{
"path": "dist/es2015/useRef.js",
"limit": "0.5 KB"
}
]

}
2 changes: 1 addition & 1 deletion src/createRef.ts
@@ -1,6 +1,6 @@
import {RefObject} from "react";

export function createCallbackRef<T>(callback: (newValue: T, lastValue: T | null) => any): RefObject<T> {
export function createCallbackRef<T>(callback: (newValue: T | null, lastValue: T | null) => any): RefObject<T> {
let current: T | null = null;

return {
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
@@ -1,2 +1,3 @@
export {useCallbackRef} from './useRef';
export {createCallbackRef} from './createRef';
export {createCallbackRef} from './createRef';
export {mergeRefs} from './mergeRef'
19 changes: 19 additions & 0 deletions src/mergeRef.ts
@@ -0,0 +1,19 @@
import * as React from 'react';
import {useCallbackRef} from "./useRef";

type ReactRef<T> = (
((newValue: T | null) => void) |
React.MutableRefObject<T | null>
);

export function mergeRefs<T>(refs: ReactRef<T>[], defaultValue?: T): React.MutableRefObject<T | null> {
return useCallbackRef<T>(defaultValue, newValue => {
refs.forEach(ref => {
if (typeof ref === 'function') {
ref(newValue)
} else if (ref != null) {
ref.current = newValue
}
})
});
}
2 changes: 1 addition & 1 deletion src/useRef.ts
@@ -1,6 +1,6 @@
import {MutableRefObject, useRef} from 'react';

export function useCallbackRef<T>(initialValue: T, callback: (newValue: T, lastValue: T) => void): MutableRefObject<T> {
export function useCallbackRef<T>(initialValue: T | null, callback: (newValue: T | null, lastValue: T | null) => void): MutableRefObject<T | null> {
const ref = useRef({
// value
value: initialValue,
Expand Down

0 comments on commit 32278f9

Please sign in to comment.