├── .editorconfig
├── .eslintrc.json
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── src
└── index.ts
├── test
└── index.test.tsx
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_style = space
3 | indent_size = 2
4 | insert_final_newline = true
5 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "browser": true,
5 | "es6": true
6 | },
7 | "globals": {
8 | "Atomics": "readonly",
9 | "SharedArrayBuffer": "readonly"
10 | },
11 | "parser": "@typescript-eslint/parser",
12 | "parserOptions": {
13 | "ecmaVersion": 2018,
14 | "sourceType": "module"
15 | },
16 | "rules": {
17 | "comma-dangle": ["error", "always-multiline"],
18 | "no-var": "error",
19 | "quotes": ["error", "single", { "avoidEscape": true }],
20 | "semi": ["error", "never"]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}
6 |
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | node: ['10.x', '12.x', '14.x']
11 | os: [ubuntu-latest, windows-latest, macOS-latest]
12 |
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v2
16 |
17 | - name: Use Node ${{ matrix.node }}
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: ${{ matrix.node }}
21 |
22 | - name: Install deps and build (with cache)
23 | uses: bahmutov/npm-install@v1
24 |
25 | - name: Test
26 | run: yarn test --ci --coverage --maxWorkers=2
27 |
28 | - name: Build
29 | run: yarn build
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Torvin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-ref-composer
2 |
3 | This package provides a simple of combining several [React refs](https://reactjs.org/docs/refs-and-the-dom.html) into a single ref that can passed to a single component or DOM node. See [React issue #13029](https://github.com/facebook/react/issues/13029) for more details.
4 |
5 | This is most useful when you want to keep a ref to a node inside your component and also forward it outside using [`React.forwardRef`](https://reactjs.org/docs/forwarding-refs.html) or pass to a library such as [`react-beautiful-dnd`](https://github.com/atlassian/react-beautiful-dnd).
6 |
7 | Both React hooks and class components are supported.
8 |
9 | ## Hooks
10 |
11 | `useRefComposer` is a hook for composing refs. Usage example:
12 |
13 | ```jsx
14 | import { useRefComposer } from 'react-ref-composer'
15 |
16 | export const MyComponent = React.forwardRef((props, outerRef) => {
17 | const innerRef = useRef()
18 | const composeRefs = useRefComposer()
19 |
20 | return
test
21 | })
22 | ```
23 |
24 | Here `composeRef` is a function that you can call with any number of refs. Both object refs and callback refs are supported. The function returns a single "combined" callback ref.
25 |
26 | Make sure to always call `composeRef` with the same number of arguments. In cases when conditional passing of a ref is required you can pass any falsy (`undefined`, `null`, `0`, `""` or `false`) value instead to temporary "turn off" the ref, e.g.:
27 | ```jsx
28 | ... ref={composeRefs(ref1, a && b && ref2)}
29 | ```
30 |
31 | ## Class components
32 |
33 | Class components work very similarly, just use `createRefComposer` instead:
34 |
35 | ```js
36 | import { createRefComposer } from 'react-ref-composer'
37 |
38 | export class MyComponent {
39 | constructor(props) {
40 | super(props)
41 | this.composeRefs = createRefComposer()
42 | }
43 |
44 | render() {
45 | return
test
46 | }
47 | }
48 | ```
49 |
50 | Same rules for `composeRef` as above apply.
51 |
52 | ## Why another library?
53 |
54 | Why create another library? The main problem with existing libraries, including [`compose-react-refs`](https://github.com/seznam/compose-react-refs), is that none of them handle changing only one of the passed refs correctly. Case in point:
55 |
56 | ```jsx
57 | function MyComponent(){
58 | const composeRefs = useRefComposer()
59 | const ref1 = useCallback(div => console.log('ref1', div), [])
60 | const ref2 = useCallback(div => console.log('ref2', div), [])
61 | const ref3 = useCallback(div => console.log('ref3', div), [])
62 | const [flag, setFlag] = useState(true)
63 |
64 | function onSwitch() {
65 | console.log('switching')
66 | setFlag(f => !f)
67 | }
68 |
69 | return
70 |
71 |
72 | }
73 | ```
74 |
75 | This is what the expected output looks like when the user clicks the button:
76 | ```jsx
77 | ref1
78 | ref2
79 | switching
80 | ref2 null
81 | ref3
82 | ```
83 |
84 | So the old ref resets to `null` and the new ref is set to the DOM node as expected.
85 |
86 | However with `compose-react-refs` and other similar libraries this happens:
87 |
88 | ```jsx
89 | ref1