Skip to content
Merged
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
15 changes: 15 additions & 0 deletions src/hooks/useId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ export function resetUuid() {
}
}

/**
* Generate a valid HTML id from prefix and key.
* Sanitizes the key by replacing invalid characters with hyphens.
* @param prefix - The prefix for the id
* @param key - The key from React element, may contain spaces or invalid characters
* @returns A valid HTML id string
*/
export function getId(prefix: string, key: string): string {
// Valid id characters: letters, digits, hyphen, underscore, colon, period
// Replace all invalid characters (including spaces) with hyphens to preserve length
const sanitizedKey = key.replace(/[^a-zA-Z0-9_.:-]/g, '-');

return `${prefix}-${sanitizedKey}`;
Comment on lines +28 to +33
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual file
cat -n src/hooks/useId.ts

Repository: react-component/util

Length of output: 2688


🏁 Script executed:

# Search for how getId is being called to understand usage patterns
rg "getId" --type ts --type tsx -B 2 -A 2

Repository: react-component/util

Length of output: 91


🌐 Web query:

React.Key type definition React 18 - what types are included in React.Key?

💡 Result:

In React 18’s TypeScript types (@types/react), React.Key is defined as:

And the JSX key attribute is typed as:

Citations:


🏁 Script executed:

# Search for getId function calls to understand usage patterns
rg "getId\(" --type ts -B 2 -A 2

Repository: react-component/util

Length of output: 1160


🏁 Script executed:

# Also check if this is exported/used elsewhere
rg "from.*useId|import.*getId" --type ts --type tsx

Repository: react-component/util

Length of output: 91


🏁 Script executed:

# Check if there are any tests or examples showing how getId should be used
fd -e test.ts -e spec.ts -e example.ts | head -20

Repository: react-component/util

Length of output: 392


React 元素 key 类型与入参类型不匹配,存在运行时风险

JSDoc 注释声称该函数处理"React 元素的 key",但 React.Key 的实际类型为 string | number | bigint(JSX 上下文中还可以是 null | undefined)。当前函数签名仅接受 string 参数,若调用者传入数字或其他类型的 React element key,将在运行时抛错。建议扩大入参类型并添加类型安全的规范化处理。

🧩 修复建议
-export function getId(prefix: string, key: string): string {
+export function getId(prefix: string, key: React.Key): string {
+  const normalizedKey = key == null ? '' : String(key);
   // Valid id characters: letters, digits, hyphen, underscore, colon, period
   // Replace all invalid characters (including spaces) with hyphens to preserve length
-  const sanitizedKey = key.replace(/[^a-zA-Z0-9_.:-]/g, '-');
+  const sanitizedKey = normalizedKey.replace(/[^a-zA-Z0-9_.:-]/g, '-');

   return `${prefix}-${sanitizedKey}`;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getId(prefix: string, key: string): string {
// Valid id characters: letters, digits, hyphen, underscore, colon, period
// Replace all invalid characters (including spaces) with hyphens to preserve length
const sanitizedKey = key.replace(/[^a-zA-Z0-9_.:-]/g, '-');
return `${prefix}-${sanitizedKey}`;
export function getId(prefix: string, key: React.Key): string {
const normalizedKey = key == null ? '' : String(key);
// Valid id characters: letters, digits, hyphen, underscore, colon, period
// Replace all invalid characters (including spaces) with hyphens to preserve length
const sanitizedKey = normalizedKey.replace(/[^a-zA-Z0-9_.:-]/g, '-');
return `${prefix}-${sanitizedKey}`;
}
🤖 Prompt for AI Agents
In `@src/hooks/useId.ts` around lines 28 - 33, Change getId to accept
React-compatible key types (e.g., string | number | bigint | null | undefined)
instead of only string, then normalize the incoming key to a safe string before
sanitization: treat null/undefined as an empty string, convert numbers and
bigints with toString(), and then apply the existing regex sanitization; update
the function signature (getId) and any callers/types accordingly so it no longer
assumes a string input.

}

const useOriginId = getUseId();

export default useOriginId
Expand Down
13 changes: 13 additions & 0 deletions tests/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { fireEvent, render } from '@testing-library/react';
import * as React from 'react';
import { renderToString } from 'react-dom/server';
import useId from '../src/hooks/useId';
import { getId } from '../src/hooks/useId';
import useLayoutEffect from '../src/hooks/useLayoutEffect';
import useMemo from '../src/hooks/useMemo';
import useMergedState from '../src/hooks/useMergedState';
Expand Down Expand Up @@ -663,6 +664,18 @@ describe('hooks', () => {
errorSpy.mockRestore();
process.env.NODE_ENV = originEnv;
});

it('should sanitize keys with invalid characters', () => {
expect(getId('item', 'hello world')).toBe('item-hello-world');
expect(getId('tab', 'user@name#123')).toBe('tab-user-name-123');
expect(getId('panel', 'test/path\\file')).toBe('panel-test-path-file');
expect(getId('menu', 'key with multiple spaces')).toBe(
'menu-key-with--multiple---spaces',
);
expect(getId('btn', 'valid-key_123:456.789')).toBe(
'btn-valid-key_123:456.789',
);
});
});

describe('useMobile', () => {
Expand Down
Loading