Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/utils/mergeRefs/ko/mergeRefs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

```ts
function mergeRefs<T>(
...refs: Array<RefObject<T> | RefCallback<T> | null | undefined>
...refs: Array<Ref<T> | undefined>
): RefCallback<T>;
```

Expand All @@ -15,7 +15,7 @@ function mergeRefs<T>(
<Interface
required
name="refs"
type="Array<RefObject<T> | RefCallback<T> | null | undefined>"
type="Array<Ref<T> | undefined>"
description="합쳐질 refs의 배열이에요. 각 ref는 RefObject 또는 RefCallback 중 하나일 수 있어요."
/>

Expand Down
4 changes: 2 additions & 2 deletions src/utils/mergeRefs/mergeRefs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This function takes multiple refs (RefObject or RefCallback) and returns a singl

```ts
function mergeRefs<T>(
...refs: Array<RefObject<T> | RefCallback<T> | null | undefined>
...refs: Array<Ref<T> | undefined>
): RefCallback<T>;
```

Expand All @@ -15,7 +15,7 @@ function mergeRefs<T>(
<Interface
required
name="refs"
type="Array<RefObject<T> | RefCallback<T> | null | undefined>"
type="Array<Ref<T> | undefined>"
description="An array of refs to be merged. Each ref can be either a RefObject or RefCallback."
/>

Expand Down
52 changes: 51 additions & 1 deletion src/utils/mergeRefs/mergeRefs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useRef } from 'react';
import { act } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';

import { renderHookSSR } from '../../_internal/test-utils/renderHookSSR.tsx';

Expand Down Expand Up @@ -75,4 +75,54 @@ describe('mergeRefs', () => {
expect(result.current.ref1.current).toBe(value);
expect(ref3Value).toBe(value);
});

it('should call cleanup functions returned by callback refs', () => {
const cleanupCalls: string[] = [];

const callbackRef1 = vi.fn(() => {
return () => {
cleanupCalls.push('cleanup1');
};
});

const callbackRef2 = vi.fn(() => {
return () => {
cleanupCalls.push('cleanup2');
};
});

const mergedRef = mergeRefs<string | null>(callbackRef1, callbackRef2);
const value = 'test-value';

act(() => {
mergedRef(value);
});

const cleanupFn = mergedRef(null);
if (cleanupFn) {
cleanupFn();
}

expect(cleanupCalls).toEqual(['cleanup1', 'cleanup2']);
expect(callbackRef1).toHaveBeenCalledWith(value);
expect(callbackRef2).toHaveBeenCalledWith(value);
});

it('verifies that object refs initialize correctly without cleanup functions', () => {
const refObj = { current: 'initial' };
const mergedRef = mergeRefs(refObj);

act(() => {
mergedRef('new-value');
});
expect(refObj.current).toBe('new-value');

const cleanupFn = mergedRef(null);
expect(cleanupFn).toBeInstanceOf(Function);

if (cleanupFn) {
cleanupFn();
}
expect(refObj.current).toBeNull();
});
});
45 changes: 34 additions & 11 deletions src/utils/mergeRefs/mergeRefs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { RefCallback, RefObject } from 'react';
import { Ref, RefCallback } from 'react';

type StrictRef<T> = NonNullable<Ref<T>>;
type RefCleanup<T> = ReturnType<RefCallback<T>>;

/**
* @description
Expand All @@ -7,7 +10,7 @@ import { RefCallback, RefObject } from 'react';
*
* @template T - The type of target to be referenced.
*
* @param {Array<RefObject<T> | RefCallback<T> | null | undefined>} refs - An array of refs to be merged. Each ref can be either a RefObject or RefCallback.
* @param {Array<Ref<T> | undefined>} refs - An array of refs to be merged. Each ref can be either a RefObject or RefCallback.
*
* @returns {RefCallback<T>} A single ref callback that updates all provided refs.
*
Expand All @@ -34,19 +37,39 @@ import { RefCallback, RefObject } from 'react';
* return <div ref={mergeRefs(measuredRef, ref)} />;
* }
*/
export function mergeRefs<T>(...refs: Array<RefObject<T> | RefCallback<T> | null | undefined>): RefCallback<T> {

function assignRef<T>(ref: StrictRef<T>, value: T | null): RefCleanup<T> {
if (typeof ref === 'function') {
return ref(value);
}

ref.current = value;
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assignRef function should return undefined when handling object refs to match the expected RefCleanup<T> return type. Add an explicit return statement.

Suggested change
ref.current = value;
ref.current = value;
return undefined;

Copilot uses AI. Check for mistakes.
}

export function mergeRefs<T>(...refs: Array<Ref<T> | undefined>): RefCallback<T> {
const availableRefs = refs.filter(ref => ref != null);
const cleanupMap = new Map<StrictRef<T>, Exclude<RefCleanup<T>, void>>();

return value => {
for (const ref of refs) {
if (ref == null) {
continue;
for (const ref of availableRefs) {
const cleanup = assignRef(ref, value);
if (cleanup) {
cleanupMap.set(ref, cleanup);
}
}

if (typeof ref === 'function') {
ref(value);
continue;
return () => {
for (const ref of availableRefs) {
const cleanup = cleanupMap.get(ref);
if (cleanup && typeof cleanup === 'function') {
cleanup();
continue;
}

assignRef(ref, null);
}

(ref as RefObject<T | null>).current = value;
}
cleanupMap.clear();
};
};
}
Loading