Skip to content

Commit

Permalink
feat(use-merge-refs): add new hook
Browse files Browse the repository at this point in the history
  • Loading branch information
andresin87 committed Mar 28, 2023
1 parent a09cece commit 1b6a91f
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/hooks/use-merge-refs/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src
**/*.stories.*
19 changes: 19 additions & 0 deletions packages/hooks/use-merge-refs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@spark-ui/use-merge-refs",
"version": "0.0.0",
"description": "check the component's mount state",
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"scripts": {
"build": "vite build"
},
"repository": {
"type": "git",
"url": "git@github.com:adevinta/spark.git",
"directory": "packages/hooks/use-mounted-state"
}
}
1 change: 1 addition & 0 deletions packages/hooks/use-merge-refs/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useMergeRefs } from './useMergeRefs'
36 changes: 36 additions & 0 deletions packages/hooks/use-merge-refs/src/useMergeRefs.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Meta, Story } from '@storybook/addon-docs'
import { StoryHeading } from '@docs/helpers/StoryHeading'

<Meta title="Hooks/useMergeRefs" />

# useMountedState

> The `useMergeRefs` hook has the purpose of merging and sync the passed references into a single one. It could be useful when implementing a component that receives a forwarded ref and that use internal refs, making it easier to pass a single ref to the DOM target.
<StoryHeading label="Install" />

```
npm install @spark-ui/use-merge-refs
```

<StoryHeading label="Import" />

```
import { useMergeRefs } from "@spark-ui/use-merge-refs"
```

<StoryHeading label="Usage" />

```jsx
import * as React from 'react'
import { useMergeRefs } from '@spark-ui/use-merge-refs'

const Demo = React.forwardRef((props, forwardedRef) => {
const firstRef = useRef()
const secondRef = useRef()

const ref = useMergeRefs(firstRef, secondRef, forwardedRef)

return <div ref={ref} {...props} />
})
```
64 changes: 64 additions & 0 deletions packages/hooks/use-merge-refs/src/useMergeRefs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { render } from '@testing-library/react'
import React, { useEffect, useRef } from 'react'
import { describe, expect, it } from 'vitest'

import { useMergeRefs } from './index'

describe('useMergeRefs', () => {
interface refsInterface {
first?: any
second?: any
third?: any
}

it('should merge all the passed refs into a single one', () => {
// Given
const refs = {} as refsInterface
function TestComponent() {
const firstRef = useRef()
const secondRef = useRef()
const thirdRef = useRef()

const ref = useMergeRefs(firstRef, secondRef, thirdRef)

useEffect(() => {
refs.first = firstRef.current
refs.second = secondRef.current
refs.third = thirdRef.current
}, [])

return <div ref={ref} />
}

render(<TestComponent />)

expect(refs.first).toBe(refs.second)
expect(refs.first).toBe(refs.third)
expect(refs.second).toBe(refs.third)
})

it('should merge both functional and object references', () => {
const refs = {} as refsInterface

const TestComponent = React.forwardRef(function TestComponent(props, forwardedRef) {
const firstRef = useRef()
const secondRef = useRef()

const ref = useMergeRefs(firstRef, secondRef, forwardedRef)

useEffect(() => {
refs.first = firstRef.current
refs.second = secondRef.current
}, [])

return <div ref={ref} {...props} />
})

const refToBeForwarded = React.createRef()
render(<TestComponent ref={node => (refToBeForwarded.current = node)} />)

expect(refs.first).toBe(refs.second)
expect(refs.first).toBe(refToBeForwarded.current)
expect(refs.second).toBe(refToBeForwarded.current)
})
})
34 changes: 34 additions & 0 deletions packages/hooks/use-merge-refs/src/useMergeRefs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { MutableRefObject, RefCallback, useMemo } from 'react'

export type ReactRef<T> = RefCallback<T> | MutableRefObject<T>

export function assignRef<T = any>(ref: ReactRef<T> | null | undefined, value: T) {
if (ref == null) {
return
}

if (typeof ref === 'function') {
ref(value)

return
}

try {
ref.current = value
} catch (error) {
throw new Error(`Cannot assign value '${value}' to ref '${ref}'`)
}
}

export function mergeRefs<T>(...refs: (ReactRef<T> | null | undefined)[]) {
return (node: T | null) => {
refs.forEach(ref => {
assignRef(ref, node)
})
}
}

export function useMergeRefs<T>(...refs: (ReactRef<T> | null | undefined)[]) {
// eslint-disable-next-line react-hooks/exhaustive-deps
return useMemo(() => mergeRefs(...refs), refs)
}
4 changes: 4 additions & 0 deletions packages/hooks/use-merge-refs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.json",
"include": ["src/**/*", "../../../global.d.ts"]
}
6 changes: 6 additions & 0 deletions packages/hooks/use-merge-refs/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import path from 'path'
import { getComponentConfiguration } from '../../../config/index'

const { name } = require(path.resolve(__dirname, 'package.json'))

export default getComponentConfiguration(name)

0 comments on commit 1b6a91f

Please sign in to comment.