Skip to content

Commit

Permalink
feat: additional API
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Oct 10, 2019
1 parent 13f9dac commit e07645c
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,2 +1,3 @@
node_modules
dist
dist
coverage
23 changes: 21 additions & 2 deletions README.md
Expand Up @@ -21,6 +21,16 @@ you may want to use ~~a callback ref instead~~ .... __useCallbackRef__ instead.

[Hooks API Reference](https://reactjs.org/docs/hooks-reference.html#useref)

This library exposes helpers to handle any case related to `ref` _lifecycle_

- `mergeRefs` - merge multiple refs together. For, actually, fork
- `transformRef` - transform one ref to anther
- `useRefCallback` - react on hook change
- `refToCallback` - convert RefObject to an old callback-style ref
- `assignRef` - assing value to the ref, regardless of it's form

All functions are tree shakable, but even together it's __less then 300b__.

# API
## useRef API
API is 99% compatible with React `createRef` and `useRef`, and just adds another argument - `callback`,
Expand Down Expand Up @@ -58,8 +68,17 @@ const refObject = useCallbackRef(null, onRefUpdate);
<SomeNewComponent ref={refObject}/>
```

## Additional API
### mergeRefs
## assignRef
`assignRef(ref, value)` - assigns `values` to the `ref`. `ref` could be RefObject or RefCallback.

## transformRef
`transformRef(ref, tranformer):Ref` - return a new `ref` which would propagate all changes to the provided `ref` with applied `transform`

## refToCallback
`refToCallback(ref: RefObject): RefCallback` - for compatibility between the old and the new code.
For the compatibility between `RefCallback` and RefObject use `useCallbackRef(undefined, callback)`

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

```js
Expand Down
44 changes: 43 additions & 1 deletion __tests__/index.tsx
@@ -1,7 +1,8 @@
import * as React from 'react';
import {createRef} from "react";
import {mount} from 'enzyme';
import {createCallbackRef, mergeRefs, useCallbackRef} from "../src";
import {createCallbackRef, mergeRefs, transformRef, useCallbackRef} from "../src";
import {retToCallback} from "../src/refToCallback";


describe('Specs', () => {
Expand Down Expand Up @@ -71,4 +72,45 @@ describe('Specs', () => {
});
});

describe('transformRef', () => {
it('composite case', () => {
const spy1 = jest.fn();
const spy2 = jest.fn();
const spy4 = jest.fn();

const ref1 = createCallbackRef<HTMLDivElement>(spy1);
const ref4t = createRef();

const TestComponent = () => {
const ref2 = createCallbackRef<string>(spy2);
const ref3 = transformRef<HTMLDivElement, string>(ref2, (current) => current!.innerHTML);
const ref4 = retToCallback<HTMLDivElement>(spy4);
const ref4s = retToCallback<HTMLDivElement>(ref4t);

return (
<div
ref={mergeRefs([
ref1,
ref3,
ref4,
ref4s
])}
>test</div>
);
};

mount(<TestComponent />).setProps({x:1}).update();

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

expect(spy1).toBeCalledWith(ref, null);
expect(spy2).toBeCalledWith("test", null);
expect(ref4t.current).toBe(ref);
expect(spy4).toBeCalledWith(ref);
})
});



});
10 changes: 10 additions & 0 deletions src/assignRef.ts
@@ -0,0 +1,10 @@
import {ReactRef} from "./types";

export function assignRef<T>(ref: ReactRef<T>, value: T | null): ReactRef<T> {
if (typeof ref === 'function') {
ref(value)
} else if (ref != null) {
ref.current = value
}
return ref;
}
4 changes: 3 additions & 1 deletion src/index.ts
@@ -1,3 +1,5 @@
export {useCallbackRef} from './useRef';
export {createCallbackRef} from './createRef';
export {mergeRefs} from './mergeRef'
export {mergeRefs} from './mergeRef'
export {assignRef} from './assignRef';
export {transformRef} from './transformRef';
13 changes: 4 additions & 9 deletions src/mergeRef.ts
@@ -1,19 +1,14 @@
import * as React from 'react';
import {useCallbackRef} from "./useRef";
import {assignRef} from "./assignRef";

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
}
})
});
return useCallbackRef<T>(defaultValue, newValue => (
refs.forEach(ref => assignRef(ref, newValue))
));
}
11 changes: 11 additions & 0 deletions src/refToCallback.ts
@@ -0,0 +1,11 @@
import {ReactRef, RefCallback} from "./types";

export function retToCallback<T>(ref: ReactRef<T>): RefCallback<T> {
return newValue => {
if (typeof ref === 'function') {
ref(newValue)
} else if (ref != null) {
ref.current = newValue
}
}
}
10 changes: 10 additions & 0 deletions src/transformRef.ts
@@ -0,0 +1,10 @@
import {ReactRef, RefObject} from "./types";
import {useCallbackRef} from "./useRef";
import {assignRef} from "./assignRef";

export function transformRef<T, K>(ref: ReactRef<K>, transformer: (original: T) => K): RefObject<T> {
return useCallbackRef<T>(
undefined,
value => assignRef(ref, transformer(value))
)
}
6 changes: 6 additions & 0 deletions src/types.ts
@@ -0,0 +1,6 @@
import * as React from "react";

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

export type ReactRef<T> = RefCallback<T> | RefObject<T>;
2 changes: 1 addition & 1 deletion tsconfig.json
Expand Up @@ -12,7 +12,7 @@
"noImplicitAny": true,
"removeComments": true,
"importHelpers": true,
"target": "es5",
"target": "es2015",
"lib": [
"dom",
"es5",
Expand Down

0 comments on commit e07645c

Please sign in to comment.