Skip to content

Conversation

@zombieJ
Copy link
Member

@zombieJ zombieJ commented Sep 2, 2025

  • React 18 以后不需要去做防御性编程,useState 更新 unmount state 不再会有 warning。
  • 移除了 onChangepostState 逻辑,代码中大部分情况都没有使用这两个特性但是却额外消耗了性能。

MAC M4 下,usePropState 会比 useMergedState 节约 ~0.01ms 性能。考虑到偏底层调度,对于 Table 嵌套子组件的场景会有一定客观的效果。

Summary by CodeRabbit

  • 新功能
    • 新增用于统一受控/非受控状态管理的 React Hook,提升组件状态处理的灵活性与一致性。
  • 文档
    • 为现有合并状态 Hook 添加弃用标注,并指引使用新的方案(在不需兼容旧环境时)。
  • 测试
    • 增加全面的用例覆盖新 Hook,包括默认值、受控/非受控切换、严格模式下行为与渲染次数等场景。
@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Walkthrough

在 hooks 中新增 usePropState,用于合并受控/非受控值;同时在 useMergedState 的 JSDoc 中标记弃用,建议迁移至 usePropState。测试文件新增针对 usePropState 的完整用例,覆盖受控/非受控切换、默认值、严格模式行为与渲染次数等。

Changes

Cohort / File(s) Summary
新 Hook:usePropState
src/hooks/usePropState.ts
新增导出 usePropState<T>(defaultStateValue, value?);内部维护 innerValue,通过 value ?? innerValue 生成 mergedValue;使用自定义 useLayoutEffect 在受控值变化后同步内部状态;返回 [mergedValue, setInnerValue]
弃用标记:useMergedState
src/hooks/useMergedState.ts
JSDoc 增加 @deprecated 注解,提示优先使用 usePropState(当不需支持 React 18 以下版本时)。无行为改动。
测试新增:usePropState 行为覆盖
tests/hooks.test.tsx
引入并测试 usePropState:受控/非受控行为、默认值(字面量与函数)、StrictMode/React 18 行为、依赖项中 setter 不触发多余渲染、受控切换与一致性验证;包含一组并行的重复用例(字符串/数字变体)。仅测试改动。

Sequence Diagram(s)

sequenceDiagram autonumber participant C as Component participant H as usePropState participant RS as React State(innerValue) participant LE as useLayoutEffect C->>H: call(defaultStateValue, value?) H->>RS: init innerValue <- defaultStateValue (lazy allowed) H->>H: mergedValue = value ?? innerValue H-->>C: return [mergedValue, setInnerValue] Note over C,H: 受控:value 已定义时以其为准 rect rgba(200,230,255,0.25) C->>LE: value changed after mount LE->>RS: setInnerValue(value) RS-->>H: update innerValue H-->>C: mergedValue reflects value end rect rgba(220,255,220,0.25) C->>H: setInnerValue(next or updater) RS-->>H: update innerValue H-->>C: mergedValue updates (若未受控) end 
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

代码田里我跳一跳,耳朵竖起听风跑,
新芽 usePropState 探头笑,
旧友 useMergedState 轻声道:先歇好。
受控不受控,月光下切换妙,
测试如繁星,数着渲染不多不少。
嗅嗅,发布吧——胡萝卜味的自豪。

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch useMergedState18

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.
@vercel
Copy link

vercel bot commented Sep 2, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
util Ready Ready Preview Comment Sep 2, 2025 8:16am
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @zombieJ, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求引入了一个新的 React Hook usePropState,旨在为 React 18 及更高版本提供一个更精简、性能更优的状态管理方案。它通过移除在 React 18 中不再需要的防御性编程和复杂状态逻辑,简化了 useMergedState 的功能,从而提升了性能,特别是在底层调度和复杂组件场景下。

Highlights

  • usePropState 新增: 引入了一个新的 React Hook usePropState,旨在提供一个更精简、性能更优的状态管理方案。
  • useMergedState 弃用: 对于 React 18 及更高版本,useMergedState 已被标记为弃用,并建议使用 usePropState
  • 逻辑简化与性能提升: 新的 usePropState 移除了 onChangepostState 逻辑,因为 React 18 后不再需要对卸载组件更新状态进行防御性编程,从而优化了性能。在 MAC M4 环境下,usePropStateuseMergedState 可节省约 0.01ms 的性能开销。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

这个 PR 引入了一个新的 usePropState hook,作为 useMergedState 的一个更轻量、更高性能的替代品,适用于 React 18+ 的环境,移除了不常用的 onChangepostState 逻辑。这是一个很好的优化。

我在代码审查中发现 usePropState 的实现在处理受控和非受控模式切换时存在一些问题,可能导致状态意外重置或跳变。我提供了一些具体的代码建议来修复这些问题,使其行为更健壮和可预测。此外,相关的单元测试中有一个断言似乎与期望的行为不符,我也提出了修改建议。

defaultStateValue: T | (() => T),
value?: T,
): [T, Updater<T>] {
const [innerValue, setInnerValue] = useState<T>(defaultStateValue);

Choose a reason for hiding this comment

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

high

innerValue 的初始状态没有考虑到组件从一开始就是受控的情况。它被初始化为 defaultStateValue,但 hook 返回的是 value prop。如果组件之后切换到非受控模式,它会回退到这个初始的 defaultStateValue,而不是最后一个有效的 prop 值,这可能会导致意外的 UI 跳变。更好的做法是,如果 value prop 存在,就用它来初始化 state。

 const [innerValue, setInnerValue] = useState<T>(() => { if (value !== undefined) { return value; } if (typeof defaultStateValue === 'function') { return (defaultStateValue as () => T)(); } return defaultStateValue; });
Comment on lines 19 to 26
useLayoutEffect(
mount => {
if (!mount) {
setInnerValue(value);
}
},
[value],
);

Choose a reason for hiding this comment

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

high

当从受控组件(有 value prop)切换到非受控组件(value prop 变为 undefined)时,这个 effect 会通过调用 setInnerValue(undefined) 来重置内部状态。这会导致组件丢失其之前的值。通常,期望的行为是保留最后一个值。为了实现这一点,effect 应该仅在 value 有定义时才同步状态。

 useLayoutEffect( mount => { if (!mount && value !== undefined) { setInnerValue(value); } }, [value], );

rerender(<FC value={undefined} />);
expect(container.querySelector('input').value).toEqual('test');
expect(container.querySelector('.txt').textContent).toEqual('');

Choose a reason for hiding this comment

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

medium

这个断言似乎不正确。val 同时用于 inputvaluespan 的内容。如果 input.value 期望是 'test',那么 spantextContent 也应该是 'test'。在 usePropState 修复为保留值的行为后,val 会是 'test'。这个断言 toEqual('') 可能是从 useMergedState 的测试中错误地复制过来的,建议将其修改为 toEqual('test') 以反映正确的预期行为。

Suggested change
expect(container.querySelector('.txt').textContent).toEqual('');
expect(container.querySelector('.txt').textContent).toEqual('test');
@codecov
Copy link

codecov bot commented Sep 2, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.23%. Comparing base (2880228) to head (262823e).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@ Coverage Diff @@ ## master #673 +/- ## ========================================== + Coverage 90.11% 90.23% +0.12%  ========================================== Files 36 37 +1 Lines 880 891 +11 Branches 288 297 +9 ========================================== + Hits 793 804 +11  Misses 85 85 Partials 2 2 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
@afc163
Copy link
Member

afc163 commented Sep 2, 2025

@gemini-code-assist @copilot @coderabbitai 给一下这个 hook 的更好的命名建议。

@gemini-code-assist
Copy link

你好 @afc163

关于 usePropState 这个 Hook 的命名建议,考虑到它主要用于在受控和非受控状态之间进行管理,以下是一些建议:

  • useControlledState: 这是最推荐的名称。它清晰地表达了 Hook 的核心功能,即处理组件的受控/非受控状态逻辑。在 React 社区中,controlled 通常指的就是通过 props 传入 value 来控制状态的模式。

  • useManagedState: 这个名称也行,但相对 useControlledState 来说,它更通用一些,可能无法立即传达出“通过 props 控制”的特定含义。

  • useExternalState: 这个名称强调了状态可以由外部(即 props)控制,但不如 useControlledState 那么直接和常见。

综合来看,useControlledState 是最能准确反映其用途且符合 React 社区习惯的命名。

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a new useControlledState hook as a performance-optimized alternative to useMergedState, leveraging React 18+ capabilities. The hook simplifies state management by removing defensive programming and unused features.

  • Adds useControlledState hook that eliminates onChange and postState logic for better performance (~0.01ms improvement)
  • Deprecates useMergedState with recommendation to use the new hook for React 18+ environments
  • Includes comprehensive test coverage for the new hook's controlled/uncontrolled behavior

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/hooks/useControlledState.ts New hook implementation with simplified state merging logic
src/hooks/useMergedState.ts Adds deprecation notice directing users to the new hook
tests/hooks.test.tsx Comprehensive test suite covering controlled/uncontrolled scenarios and edge cases

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


rerender(<FC value={undefined} />);
expect(container.querySelector('input').value).toEqual('test');
expect(container.querySelector('.txt').textContent).toEqual('');
Copy link

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

This assertion expects an empty string, but based on line 350 the input value remains 'test' when value becomes undefined. The text content should likely be 'test' to match the actual behavior, or there's a bug in the hook implementation.

Suggested change
expect(container.querySelector('.txt').textContent).toEqual('');
expect(container.querySelector('.txt').textContent).toEqual('test');
Copilot uses AI. Check for mistakes.
defaultStateValue: T | (() => T),
value?: T,
): [T, Updater<T>] {
const [innerValue, setInnerValue] = useState<T>(defaultStateValue);
Copy link

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

When value is undefined and defaultStateValue is a function, the function will be called on every render instead of only on initialization. This could cause performance issues and unexpected behavior. Consider using a lazy initial state pattern or memoization.

Suggested change
const [innerValue, setInnerValue] = useState<T>(defaultStateValue);
const [innerValue, setInnerValue] = useState<T>(
typeof defaultStateValue === 'function'
// @ts-ignore
? () => (defaultStateValue as () => T)()
: () => defaultStateValue
);
Copilot uses AI. Check for mistakes.
@afc163
Copy link
Member

afc163 commented Sep 2, 2025

@coderabbitai 给一下这个 hook 的更好的命名建议。

@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@afc163 基于我对 usePropState 实现的分析,我建议以下几个更好的命名选项:

推荐命名(按优先级排序)

1. useControllableState ⭐⭐⭐

  • 理由:准确描述了这个 hook 的核心功能 - 处理可控制的状态(受控/非受控模式)
  • 业界惯例:这个命名在 React 生态系统中广泛使用(如 Ariakit、Radix UI 等)
  • 语义清晰:明确表达了状态的"可控制性"特征

2. useControlledValue ⭐⭐

  • 理由:强调处理受控值的特性,简洁明了
  • 适用场景:特别适合表单组件和输入组件的状态管理

3. useValueState ⭐⭐

  • 理由:突出这是一个专门处理值状态的 hook
  • 简洁性:比较短,容易记忆

4. useFlexibleState

  • 理由:体现了灵活处理受控/非受控的特性
  • 通用性:含义相对宽泛,适用性强

不建议的命名

  • usePropState - 当前名称不够直观,"Prop" 可能让人误解为所有 props 的状态
  • useSimpleState - 过于简单,没有体现核心特性
  • useLightState - 性能优化的含义不够明确

最终建议

我强烈推荐使用 useControllableState,因为:

  1. 与业界标准命名一致
  2. 语义清晰,一看就知道用途
  3. 准确描述了受控/非受控的双重特性
  4. 便于开发者理解和采用

您觉得这些建议如何?

 ∩―――∩ ( ◕ ◕ ) o_) # (_o \ / \ / \ / \/ 

🏁 Script executed:

#!/bin/bash # First, let's find the usePropState implementation fd -t f -e js -e ts -e jsx -e tsx | xargs rg -l "usePropState" 

Length of output: 62


🏁 Script executed:

#!/bin/bash # Let's examine the usePropState implementation cat src/hooks/usePropState.ts 

Length of output: 89


🏁 Script executed:

#!/bin/bash # Let's also look at the test file to better understand the intended behavior cat tests/usePropState.test.tsx 

Length of output: 93


🏁 Script executed:

#!/bin/bash # Let's see the current useMergedState for comparison cat src/hooks/useMergedState.ts 

Length of output: 2293


🏁 Script executed:

#!/bin/bash # Check if there are any other related files or documentation rg -n "usePropState" --type md --type ts --type tsx --type js --type jsx -A2 -B2 

Length of output: 108


🏁 Script executed:

#!/bin/bash # Let's search for all files in the repository and look for usePropState find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.md" \) -exec grep -l "usePropState" {} \; 

Length of output: 139


🏁 Script executed:

#!/bin/bash # Let's check the directory structure to understand the layout ls -la find . -name "*.ts" -o -name "*.tsx" | head -20 

Length of output: 1645


🏁 Script executed:

#!/bin/bash # Search for usePropState with basic grep grep -r "usePropState" . --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" --include="*.md" 

Length of output: 111


🏁 Script executed:

#!/bin/bash # Let's look at the PR diff to see what files were changed git diff --name-only HEAD~1 HEAD 2>/dev/null || git log --oneline -1 

Length of output: 79


🏁 Script executed:

#!/bin/bash # Let's check what hooks exist in the src directory find . -path "*/hooks/*" -name "*.ts" -o -path "*/hooks/*" -name "*.tsx" | sort 

Length of output: 346


🏁 Script executed:

#!/bin/bash # I see there's a useControlledState.ts - let's examine it as it might be the renamed usePropState cat ./src/hooks/useControlledState.ts 

Length of output: 913


🏁 Script executed:

#!/bash # Let's also check the recent git commits to see what was renamed git log --oneline -10 

Length of output: 45


🏁 Script executed:

#!/bin/bash # Let's check if there are any test files for useControlledState find . -name "*test*" -type f | xargs grep -l "useControlledState" 2>/dev/null || echo "No test files found" 

Length of output: 89


🏁 Script executed:

#!/bin/bash # Let's look at the git diff for the recent commit git show --name-status HEAD 

Length of output: 2289


🏁 Script executed:

#!/bin/bash # Let's check the entire git log to understand the renaming git log --follow --oneline -- src/hooks/useControlledState.ts 

Length of output: 85


@afc163 我看到这个 hook 已经被重命名为 useControlledState 了!让我分析一下当前的命名以及其他可能的选择:

当前命名分析:useControlledState ⭐⭐⭐⭐⭐

优点:

  • 语义准确:准确描述了处理受控状态的功能
  • 业界标准:与 React 生态系统中的通用命名保持一致
  • 简洁明了:比 usePropState 更直观地表达了用途
  • 区别明确:与 useMergedState 形成清晰对比

基于实现代码分析,这个 hook 的核心特点:

  • 简化版的状态管理,移除了 onChangepostState 逻辑
  • 专注于受控/非受控模式的基础合并
  • React 18+ 优化,无需防御性编程

如果还想考虑其他选项:

1. useValueState ⭐⭐⭐

// 强调处理值状态 const [value, setValue] = useValueState(defaultValue, propValue);

2. useControllableValue ⭐⭐⭐

// 更强调值的可控制性 const [value, setValue] = useControllableValue(defaultValue, propValue);

3. useSimpleState ⭐⭐

// 强调相比 useMergedState 的简化特性 const [value, setValue] = useSimpleState(defaultValue, propValue);

我的建议

保持当前的 useControlledState 是最佳选择,因为:

  1. 命名准确性:完美描述了受控组件模式的状态管理
  2. 生态系统一致性:与其他知名库(Ariakit、Radix UI 等)的命名保持一致
  3. 用户理解成本低:React 开发者一看就知道这是处理受控状态的 hook
  4. 未来维护性:清晰的命名有助于长期维护

当前的命名选择非常棒!👍

 ∩―――∩ ( ◕ ◕ ) o_) 💡 (_o \ / \ / \ / \/ 

🏁 Script executed:

#!/bin/bash # Let's check the test file to understand the hook better cat ./tests/hooks.test.tsx 

Length of output: 19724


🏁 Script executed:

#!/bin/bash # Let's also check if there's any documentation about the hook cat README.md | grep -A5 -B5 -i "controlled\|state\|hook" || echo "No relevant documentation found" 

Length of output: 132


🏁 Script executed:

#!/bin/bash # Let's check if there are multiple commits before this one that might show the evolution git log --all --oneline | head -20 

Length of output: 95


🏁 Script executed:

#!/bin/bash # Let's check the index.ts to see what's being exported cat src/index.ts | grep -i "controlled\|state" 

Length of output: 116


🏁 Script executed:

#!/bin/bash # Let's examine the test file for usage patterns grep -A10 -B5 "useControlledState\|describe\|it(" ./tests/hooks.test.tsx 

Length of output: 13666


@afc163 现在我看到了完整的情况!这个 hook 已经被很好地重命名为 useControlledState 了,我要说这个命名选择非常棒!🎉

为什么 useControlledState 是最佳选择:

API 设计完美契合命名

// 清晰直观的 API const [value, setValue] = useControlledState(defaultValue, controlledValue); // vs 复杂的 useMergedState const [value, setValue] = useMergedState(defaultValue, { value: controlledValue, onChange, postState });

业界标准命名

  • Ariakit: useControlledState
  • Radix UI: useControllableState
  • React Hook Form: useController
  • 您的选择与顶级库保持一致!

语义准确性

  • 准确描述了"受控状态"的核心概念
  • 一看就知道是处理受控/非受控组件模式
  • usePropState 更直观易懂

性能优化体现

从测试代码可以看出,相比 useMergedState

  • 移除了 onChangepostState 逻辑
  • 更简洁的实现,更好的性能
  • React 18+ 优化,无需防御性编程

一个小建议 💡

我注意到 useControlledState 还没有从 src/index.ts 中导出。是否需要添加导出?

// src/index.ts export { default as useControlledState } from './hooks/useControlledState';

总结

useControlledState 这个命名是完美的选择!它:

  • 🎯 语义清晰准确
  • 🏭 符合业界标准
  • 🚀 体现性能优化
  • 🧠 降低学习成本

恭喜做出了这么好的命名决策!👏

 ∩―――∩ ( ◕ ◕ ) o_) 👍 (_o \ / \ / \ / \/ 

🏁 Script executed:

#!/bin/bash # Let's check if useControlledState is exported from the main index grep -n "useControlledState" src/index.ts 

Length of output: 41


看起来我的回复已经很全面了!确认了 useControlledState 确实没有从主入口文件导出,这意味着这个优秀的 hook 还没有对外暴露。

您对当前的 useControlledState 命名满意吗?还是想考虑其他的命名选项?从技术角度来说,我认为这已经是最佳选择了! 🎯

@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@afc163 我来分析一下 usePropState 这个 hook 的实现,然后提供更好的命名建议。

基于 PR 的描述和目标,这个 hook 的主要特点是:

  1. 更简洁的受控/非受控状态管理:移除了 onChangepostState 逻辑
  2. 性能优化:比 useMergedState 节约 ~0.01ms
  3. React 18+ 优化:不需要防御性编程,移除了 unmount warning 处理

命名建议:

1. useControlledState

推荐指数:⭐⭐⭐⭐⭐

  • 优势:直接表达核心功能 - 处理受控状态
  • 符合 React 生态命名习惯
  • 语义清晰,一眼就能看出用途

2. useSimpleState

推荐指数:⭐⭐⭐⭐

  • 优势:体现了相比 useMergedState 的简化特性
  • 简洁明了
  • 暗示性能更好、功能更纯粹

3. useManagedState

推荐指数:⭐⭐⭐

  • 优势:表达了对状态的智能管理
  • 通用性好
  • 专业术语,符合状态管理概念

4. useStateProp

推荐指数:⭐⭐

  • 优势:从另一个角度表达 prop 和 state 的结合
  • 简短
  • 但语义不如其他选项清晰

最终推荐:

考虑到这个 hook 的核心价值是简化受控/非受控状态管理,我推荐使用 useControlledState,因为:

  1. 语义精确:直接表达了受控状态管理的核心功能
  2. 社区认知度高:controlled/uncontrolled 是 React 社区熟知的概念
  3. 与现有 API 呼应:和 value/defaultValue 的受控模式概念一致
  4. 便于理解:新开发者看到这个名字就能理解用途

这个命名既体现了功能特性,又符合 React 生态的命名习惯,是最佳选择。


🏁 Script executed:

#!/bin/bash # Find and examine the usePropState hook implementation fd -e ts -e tsx -e js -e jsx | xargs rg -l "usePropState" | head -5 

Length of output: 67


🏁 Script executed:

#!/bin/bash # Look at the usePropState implementation ast-grep --pattern 'function usePropState($$$) { $$$ }' ast-grep --pattern 'const usePropState = ($$$) => { $$$ }' ast-grep --pattern 'export $_ usePropState' 

Length of output: 164


🏁 Script executed:

#!/bin/bash # Find files containing usePropState and examine the content rg -n -A10 -B5 "usePropState" --type=ts --type=js 

Length of output: 49


🏁 Script executed:

#!/bin/bash # Look at the src directory structure to find the hook fd -t f . src/ | grep -E "\.(ts|tsx|js|jsx)$" | head -10 cat src/index.ts 2>/dev/null || echo "index.ts not found" 

Length of output: 622


🏁 Script executed:

#!/bin/bash # Find the hook files fd "usePropState" . fd -e ts -e js | xargs rg -l "usePropState" 

Length of output: 63


🏁 Script executed:

#!/bin/bash # Look for hook implementations in common directories find . -name "*.ts" -o -name "*.js" | head -20 cat ./src/hooks/usePropState.ts 2>/dev/null || echo "usePropState.ts not found in src/hooks" cat ./src/usePropState.ts 2>/dev/null || echo "usePropState.ts not found in src" 

Length of output: 706


🏁 Script executed:

#!/bin/bash # Search more broadly for the hook implementation rg -r '$1' 'export.*usePropState' -A20 

Length of output: 40


🏁 Script executed:

#!/bin/bash # List all TypeScript files to understand the structure fd -e ts . | sort 

Length of output: 1056


🏁 Script executed:

#!/bin/bash # Examine the main files to understand the hook cat src/hooks/usePropState/index.ts 2>/dev/null || echo "not found" cat src/hooks/usePropState.ts 2>/dev/null || echo "not found" cat src/usePropState.ts 2>/dev/null || echo "not found" 

Length of output: 182


🏁 Script executed:

#!/bin/bash # Check the test files to understand the hook behavior rg -n -A15 "usePropState" tests/ || echo "No tests directory found" fd test -t d | head -5 

Length of output: 133


🏁 Script executed:

#!/bin/bash # Look for test files and examine them fd -e test.ts -e test.tsx -e test.js -e spec.ts | head -5 cat tests/usePropState.test.ts 2>/dev/null || echo "test file not found" cat test/usePropState.test.ts 2>/dev/null || echo "test file not found" cat __tests__/usePropState.test.ts 2>/dev/null || echo "test file not found" 

Length of output: 428


🏁 Script executed:

#!/bin/bash # Try different test directory patterns find . -name "*test*" -type f | head -10 find . -name "*usePropState*" -type f 

Length of output: 350


🏁 Script executed:

#!/bin/bash # Look at all files in the repository to understand the structure ls -la find . -maxdepth 2 -name "*.ts" -o -name "*.tsx" | head -15 

Length of output: 1447


🏁 Script executed:

#!/bin/bash # Check if there are TypeScript files with usePropState content rg -l usePropState . 2>/dev/null || echo "No files found with usePropState" # Try to find the source files cat src/hooks/usePropState/index.ts 2>/dev/null || cat src/hooks/usePropState.ts 2>/dev/null || cat src/usePropState/index.ts 2>/dev/null || echo "Could not find usePropState source" 

Length of output: 278


🏁 Script executed:

#!/bin/bash # Check git status to see what files were changed in this PR git log --oneline -n 5 git diff HEAD~1 --name-only | head -10 

Length of output: 274


🏁 Script executed:

#!/bin/bash # Look at recent commits to see what files were added/modified git show --name-only HEAD git diff HEAD~1..HEAD --name-only 

Length of output: 2332


🏁 Script executed:

#!/bin/bash # Examine the changed files git diff HEAD~1..HEAD | head -100 

Length of output: 228


@afc163 我发现了!在文件列表中看到了 src/hooks/useControlledState.ts,这应该就是您提到的新 hook。让我检查一下实现细节:

基于目前发现的 useControlledState hook,我来为您提供一些更好的命名建议:

当前分析:

如果这个 hook 是 useControlledState 的话,从命名角度来看已经相当不错了。但基于 PR 目标中提到的性能优化简化特性,我建议以下几个更好的命名:

命名建议:

1. usePropState (原建议)

推荐指数:⭐⭐⭐⭐

  • 优势:简洁明了,直接体现 prop 到 state 的转换
  • 符合 hook 简化的理念
  • 易于理解和记忆

2. useValue

推荐指数:⭐⭐⭐⭐⭐

  • 优势:极简命名,体现最纯粹的值管理
  • 性能导向:名字本身就暗示了轻量级
  • 通用性强:不限制使用场景
  • 符合 React 18+ 的简化趋势

3. useSimpleState

推荐指数:⭐⭐⭐⭐

  • 优势:直接表达了相对于 useMergedState 的简化特性
  • 性能暗示:simple 意味着更快
  • 语义清晰

4. useStateValue

推荐指数:⭐⭐⭐

  • 优势:结合了 state 和 value 的概念
  • 明确表达功能
  • 但稍显冗长

最终推荐:

考虑到您的 hook 主要特点是性能优化移除冗余功能,我最推荐 useValue,原因:

  1. 极简风格:体现了去除冗余的设计理念
  2. 性能暗示:简短的名字暗示轻量级实现
  3. 现代感:符合 React 18+ 的简化趋势
  4. 通用性:不局限于特定使用场景
  5. 记忆友好:短小精悍,容易记住

如果您希望保持更明确的语义,usePropState 也是很好的选择,直接表达了受控/非受控状态的管理功能。

您觉得哪个命名更符合您的预期?


🏁 Script executed:

#!/bin/bash # I notice there's a useControlledState.ts file - let's examine it as it might be the hook in question cat src/hooks/useControlledState.ts 

Length of output: 911


🏁 Script executed:

#!/bin/bash # Check if usePropState was mentioned in any of the git commits or branches git log --all --grep="usePropState" --oneline git branch -a | grep -i prop git log --all --grep="propState" --oneline 

Length of output: 119


🏁 Script executed:

#!/bin/bash # Let's check the current index.ts to see what hooks are exported cat src/index.ts 

Length of output: 349


🏁 Script executed:

#!/bin/bash # Check the test files to see if there are any references to the new hook rg -i "propstate\|controlledstate" tests/ -n -A5 -B5 

Length of output: 54


🏁 Script executed:

#!/bin/bash # Look at the package.json and recent commit history to understand what changed cat package.json | head -20 git log --oneline -n 10 

Length of output: 542


🏁 Script executed:

#!/bin/bash # Since we see useControlledState.ts in the file listing, let's examine it and the tests cat src/hooks/useControlledState.ts find tests/ -name "*controlled*" -o -name "*prop*" 

Length of output: 964


🏁 Script executed:

#!/bin/bash # Look for any test files that might test the new hook rg -i "controlled" tests/ -l cat tests/hooks.test.tsx | head -50 

Length of output: 1822


🏁 Script executed:

#!/bin/bash # Examine the actual implementation of the hook cat src/hooks/useControlledState.ts 

Length of output: 911


🏁 Script executed:

#!/bash # Check if this hook is being exported in the main index grep -n "useControlledState\|usePropState" src/index.ts 

Length of output: 57


🏁 Script executed:

#!/bin/bash # Look at any relevant test files find tests/ -name "*controlled*" -type f rg -n "useControlledState" tests/ -A10 -B5 

Length of output: 6087


完美!现在我看到了完整的实现。让我基于实际的代码给出更精准的命名建议:

@afc163 现在我看到了 useControlledState 的完整实现!基于实际代码分析,我来给出更精准的命名建议:

代码分析:

这个 hook 的核心特点:

  • 极简设计:移除了 onChangepostState 逻辑
  • 性能优化:专为 React 18+ 优化,去除了防御性编程
  • 受控/非受控统一处理value !== undefined ? value : innerValue
  • 同步机制:使用 useLayoutEffect 同步外部值变化

命名建议排序:

1. usePropState ⭐⭐⭐⭐⭐

最推荐 - 符合 PR 目标

// 直观表达:prop → state 的转换管理 const [value, setValue] = usePropState(defaultValue, propValue);
  • PR 目标一致:正是您在目标中提到的命名
  • 语义精准:prop 和 state 的融合管理
  • 简洁明了:比 useControlledState 更短
  • 性能暗示:名字暗示轻量级实现

2. useValue ⭐⭐⭐⭐

极简风格

// 最纯粹的值管理 const [value, setValue] = useValue(defaultValue, propValue);
  • 极简设计:体现去除冗余特性的理念
  • 通用性强:不限制使用场景
  • 现代感:符合 React 18+ 简化趋势

3. useSimpleState ⭐⭐⭐

对比导向

// 明确表达相对于 useMergedState 的简化 const [value, setValue] = useSimpleState(defaultValue, propValue);
  • 明确定位:突出与 useMergedState 的区别
  • 性能含义:simple = fast

4. useControlledState ⭐⭐

当前命名

  • 语义清晰:受控状态管理
  • 稍显冗长:相比其他选项

最终建议:

基于您的 PR 目标明确提到 "Support usePropState",我强烈推荐使用 usePropState

这个命名:

  1. 与 PR 目标完全一致 🎯
  2. 简洁而精准 - 直接表达 prop→state 管理
  3. 性能友好 - 简短的命名暗示轻量实现
  4. 易于迁移 - 从 useMergedState 迁移时语义更清晰
// 推荐的最终 API export default function usePropState<T>( defaultStateValue: T | (() => T), value?: T, ): [T, Updater<T>]

这样既符合您的初始设想,又能清晰表达这个 hook 的核心价值!

@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@afc163 基于您的 usePropState hook 的设计目标和功能,我为您提供以下几个更好的命名建议:

推荐命名(按优先级排序)

1. useControlState

推荐理由:

  • 简洁明了,直接表达"受控状态"的概念
  • 与 React 生态的命名习惯一致
  • 避免了 "Prop" 这个词可能带来的歧义

2. useFlexState

推荐理由:

  • 体现了状态管理的灵活性(flexible)
  • 简短易记,符合 hook 命名规范
  • 暗示可以在受控/非受控之间灵活切换

3. useAdaptiveState

推荐理由:

  • 强调状态的自适应特性
  • 能够根据 props 存在与否自动适配受控/非受控模式
  • 语义更加准确

4. useSmartState

推荐理由:

  • 突出智能化的状态管理
  • 体现了相比 useMergedState 的性能优化
  • 简洁且容易理解

不太推荐的选项

  • usePropState (当前名称) - "Prop" 容易让人误解为只处理 props
  • useUnifiedState - 过于抽象,不够直观
  • useDualState - 意义不够明确

最终建议

我最推荐 useControlState,因为:

  1. 准确表达了受控状态管理的核心功能
  2. 与 React 社区的命名惯例保持一致
  3. 简洁明了,便于开发者理解和使用
  4. 避免了歧义,一看就知道是用来处理受控/非受控状态的

您觉得这些建议如何?


🏁 Script executed:

#!/bin/bash # Find the new usePropState hook implementation fd -e ts -e tsx -e js -e jsx | xargs rg -l "usePropState" | head -10 

Length of output: 68


🏁 Script executed:

#!/bin/bash # Look for the hook implementation in src directory fd "usePropState" --type f 

Length of output: 26


🏁 Script executed:

#!/bin/bash # Search for any files containing usePropState rg -n "usePropState" --type ts --type tsx --type js --type jsx -C 5 

Length of output: 95


🏁 Script executed:

#!/bin/bash # Look at the entire src directory structure find src -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" | head -20 

Length of output: 503


🏁 Script executed:

#!/bin/bash # Check what files are in the repository root and src ls -la echo "--- src directory ---" ls -la src/ 2>/dev/null || echo "No src directory found" 

Length of output: 2544


🏁 Script executed:

#!/bash # Look for the hook files more broadly find . -name "*.ts" -o -name "*.tsx" | xargs rg -l "usePropState\|useMergedState" | head -10 

Length of output: 94


🏁 Script executed:

#!/bin/bash # Search for hook files and examine the test files as they often show usage rg -n "usePropState" -A 10 -B 5 

Length of output: 31


🏁 Script executed:

#!/bin/bash # Let's look at the PR changes to understand what was added git log --oneline -10 echo "--- Recent changes ---" git show --name-only HEAD 

Length of output: 2204


🏁 Script executed:

#!/bin/bash # Look at the files changed in this PR git diff --name-only HEAD~1..HEAD echo "--- Files modified ---" git diff HEAD~1..HEAD --stat 

Length of output: 507


🏁 Script executed:

#!/bin/bash # Examine the actual implementation git show HEAD | head -200 

Length of output: 4594


🏁 Script executed:

#!/bin/bash # Let's see the current files in the repo ls -la echo "--- Looking for TypeScript files ---" find . -name "*.ts" | head -10 

Length of output: 1450


🏁 Script executed:

#!/bin/bash # Look at the git diff to see what was added git diff HEAD~1..HEAD | head -100 

Length of output: 228


🏁 Script executed:

#!/bin/bash # Check the src/hooks directory specifically ls -la src/hooks/ 2>/dev/null echo "--- Contents of lib directory ---" ls -la lib/ 2>/dev/null echo "--- All TypeScript files ---" find . -name "*.ts" -type f 

Length of output: 2119


🏁 Script executed:

#!/bin/bash # Look for hook implementations in any TypeScript file cat lib/hooks/usePropState.ts 2>/dev/null || echo "File not found" echo "--- Available TypeScript files ---" find . -name "*.ts" -exec basename {} \; 

Length of output: 893


🏁 Script executed:

#!/bin/bash # Let's examine all TypeScript files for file in $(find . -name "*.ts" -type f); do echo "=== $file ===" head -20 "$file" echo "" done 

Length of output: 36862


@afc163 根据我对您的 useControlledState hook 的分析,我为您提供以下更好的命名建议:

推荐命名(按优先级排序)

1. useControllableState ⭐⭐⭐⭐⭐

最推荐理由:

  • 准确表达"可控制的状态"概念,既可受控也可非受控
  • 与 React 社区命名习惯完全一致
  • 语义清晰,一看就知道是处理可控/非控状态的
  • 避免与现有的 useControlledState 混淆

2. useManagedState ⭐⭐⭐⭐

推荐理由:

  • 体现了状态的统一管理特性
  • 简洁明了,容易理解
  • 暗示这是一个更高级的状态管理方案

3. useFlexibleState ⭐⭐⭐

推荐理由:

  • 突出状态管理的灵活性
  • 能够灵活切换受控/非受控模式
  • 符合 hook 命名规范

4. useAdaptiveState ⭐⭐⭐

推荐理由:

  • 强调自适应特性
  • 能够根据 props 自动适配模式
  • 体现了智能化的设计理念

基于您的代码分析

看了您的 useControlledState 实现,这个 hook 的核心特点是:

// 核心逻辑:自动选择受控或非受控模式 const mergedValue = value !== undefined ? value : innerValue;
  • ✅ 简化了 useMergedState 的复杂逻辑
  • ✅ 移除了 onChangepostState 回调
  • ✅ 针对 React 18+ 优化性能
  • ✅ 统一处理受控/非受控状态

最终建议

我强烈推荐使用 useControllableState,因为:

  1. 语义准确:准确描述了"可以被控制的状态"这一核心功能
  2. 社区认知:与 React 生态系统的命名惯例高度一致
  3. 避免歧义:比当前的 useControlledState 更能体现双模式特性
  4. 易于理解:开发者一看名字就知道用途

这个名字既体现了 hook 的核心价值(统一受控/非受控状态管理),又保持了简洁性,非常适合作为 useMergedState 的现代化替代方案。

您觉得这个建议如何?


🏁 Script executed:

#!/bin/bash # Let's look at the useControlledState hook since it seems to be the new hook based on the PR description cat src/hooks/useControlledState.ts 

Length of output: 911


🏁 Script executed:

#!/bin/bash # Let's also check the useMergedState to understand the differences cat src/hooks/useMergedState.ts 

Length of output: 2293


🏁 Script executed:

#!/bin/bash # Check if there are any test files for these hooks find tests -name "*hook*" -o -name "*state*" | head -10 

Length of output: 102


🏁 Script executed:

#!/bin/bash # Look at the test files that might show usage patterns cat tests/hooks.test.tsx 2>/dev/null || echo "File not found" cat tests/hooks-17.test.tsx 2>/dev/null || echo "File not found" 

Length of output: 21298


🏁 Script executed:

#!/bin/bash # Let's check the git log to understand what was added or renamed git log --oneline --since="1 week ago" | head -10 

Length of output: 73


🏁 Script executed:

#!/bin/bash # Let's check the package.json to see if there are clues about the new feature cat package.json | grep -A5 -B5 "test\|script" 

Length of output: 1482


@afc163 我看到您的 hook 实际上已经命名为 useControlledState,基于我对代码和测试用例的分析,为您提供以下更好的命名建议:

推荐命名(按优先级排序)

1. useControllableState ⭐⭐⭐⭐⭐

最强推荐理由:

  • 比当前的 useControlledState 更准确表达"可控制的"概念
  • 突出了既可受控也可非受控的双重特性
  • 与 React 社区命名习惯完全一致(如 Headless UI、Radix UI 等都用这个命名)
  • 语义更加精确

2. useManagedState ⭐⭐⭐⭐

推荐理由:

  • 体现了统一管理受控/非受控状态的核心功能
  • 简洁明了,比 useMergedState 更现代化
  • 暗示这是一个更智能的状态管理方案

3. useFlexibleState ⭐⭐⭐

推荐理由:

  • 强调状态管理的灵活性
  • 体现了可以灵活切换模式的特点
  • 符合现代 React Hook 命名趋势

基于您的实际代码分析

查看您的 useControlledState 实现,其核心特点是:

// 关键逻辑:自动选择使用 prop 值或内部状态 const mergedValue = value !== undefined ? value : innerValue;

相比 useMergedState 的优势:

  • ✅ 移除了复杂的 onChange 回调逻辑
  • ✅ 去掉了 postState 转换逻辑
  • ✅ 针对 React 18+ 优化,不需要防御性编程
  • ✅ 性能提升约 0.01ms(M4 芯片测试)
  • ✅ 代码更简洁,维护成本更低

最终建议

我强烈建议改名为 useControllableState,原因:

  1. 语义准确性useControlledState 容易让人以为只处理受控状态,而 useControllableState 准确表达了"可以被控制"的双重特性

  2. 社区标准:许多知名 React 库都采用这个命名:

    • Headless UI 的 useControllableState
    • Radix UI 的 useControllableState
    • React Hook Form 相关生态
  3. API 设计清晰:一看名字就知道这个 Hook 会根据是否传入 value 自动切换模式

  4. 向后兼容:可以平滑替代 useMergedState,开发者迁移成本低

您觉得 useControllableState 这个命名如何?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/hooks/useMergedState.ts (1)

16-19: 弃用注释措辞改进(更清晰的 React 版本条件)

当前文案略显别扭:“if not need support < React 18”。建议调整为更清晰的表述。

- * @deprecated Please use `useControlledState` instead if not need support < React 18. + * @deprecated If you do not need to support React < 18, please use `useControlledState` instead.
src/hooks/useControlledState.ts (2)

6-10: 命名建议:更贴近社区心智与 rc- 生态*

可选命名(按推荐度排序):

  • useControllableState(社区广泛使用,语义直观)
  • useControllableValue(rc-* 家族常见“Value”表述,易迁移)
  • usePropState(呼应 PR 文案,强调由 props 驱动)
  • useMergedPropState(强调合并外部/内部)

如需我批量替换命名并更新测试、文档,可直接提示我生成对应变更。


6-10: JSDoc 英文小修(语法与措辞)

更自然的英文表述如下:

 /** * Similar to `useState` but will use props value if provided. - * From React 18, we do not need safe `useState` since it will not throw for unmounted update. - * This hooks remove the `onChange` & `postState` logic since we only need basic merged state logic. + * Since React 18, a "safe" `useState` is no longer required because setState after unmount no longer warns. + * This hook removes the `onChange` and `postState` logic, keeping only the basic merged state semantics. */
tests/hooks.test.tsx (1)

441-462: 拼写修正:alway -> always(测试名称)

建议修正测试标题中的小拼写错误,便于搜索与一致性。

- it('should alway use option value', () => { + it('should always use option value', () => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 62e1415 and 262823e.

📒 Files selected for processing (3)
  • src/hooks/useControlledState.ts (1 hunks)
  • src/hooks/useMergedState.ts (1 hunks)
  • tests/hooks.test.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/hooks.test.tsx (1)
src/hooks/useControlledState.ts (1)
  • useControlledState (11-34)
🪛 Biome (2.1.2)
tests/hooks.test.tsx

[error] 468-468: Unexpected empty array pattern.

(lint/correctness/noEmptyPattern)

🔇 Additional comments (1)
src/hooks/useControlledState.ts (1)

15-18: 核心逻辑 LGTM

内部状态与受控值合并逻辑简单直接,默认值懒初始化也符合预期。

Comment on lines +468 to +475
const [] = useControlledState(undefined);
count += 1;
return null;
};

render(<Demo />);
expect(count).toBe(1);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

修复 Biome 报错:Unexpected empty array pattern

空数组解构会触发 lint/correctness/noEmptyPattern。这里无需接收返回值,直接调用即可。

- const [] = useControlledState(undefined); + useControlledState(undefined);
📝 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
const [] = useControlledState(undefined);
count += 1;
return null;
};
render(<Demo />);
expect(count).toBe(1);
});
useControlledState(undefined);
count += 1;
return null;
};
render(<Demo />);
expect(count).toBe(1);
});
🧰 Tools
🪛 Biome (2.1.2)

[error] 468-468: Unexpected empty array pattern.

(lint/correctness/noEmptyPattern)

🤖 Prompt for AI Agents
In tests/hooks.test.tsx around lines 468 to 475, the line using an empty array destructuring "const [] = useControlledState(undefined);" triggers Biome lint rule noEmptyPattern; remove the empty destructuring and simply call useControlledState(undefined) directly (i.e., replace the const [] = ... with a plain call), so the hook is invoked without assigning its return value. 
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
tests/hooks.test.tsx (1)

465-465: 修复空数组模式的静态分析警告

静态分析工具检测到空数组解构模式。虽然这在 JavaScript 中是有效的,但可以通过添加注释来抑制警告。

- const [] = usePropState(undefined); + const [] = usePropState(undefined); // eslint-disable-line @typescript-eslint/no-unused-vars

或者使用更明确的变量名:

- const [] = usePropState(undefined); + const [_unused] = usePropState(undefined);
src/hooks/useMergedState.ts (1)

16-16: 弃用说明英文表述不清,建议润色

当前文案有语法问题,且“< React 18”支持条件表述不够自然。建议修改为更清晰的英文说明,便于用户理解迁移条件。

- * @deprecated Please use `usePropState` instead if not need support < React 18. + * @deprecated Prefer `usePropState` if you do not need to support React < 18.
src/hooks/usePropState.ts (2)

4-4: 复用 React 内置类型,避免自定义重复定义

Updater<T>React.Dispatch<React.SetStateAction<T>> 等价。为减少重复定义与未来维护成本,建议直接使用 React 内置类型。

-import { useState } from 'react'; +import { useState, type Dispatch, type SetStateAction } from 'react'; -type Updater<T> = (updater: T | ((origin: T) => T)) => void; +type Updater<T> = Dispatch<SetStateAction<T>>;

7-10: JSDoc 英文措辞改进(准确性与语法)

  • “hooks remove” 单复数不一致;
  • React 18 的行为是“不再警告(warn)”,而非“不再抛错(throw)”。
 /** - * Similar to `useState` but will use props value if provided. - * From React 18, we do not need safe `useState` since it will not throw for unmounted update. - * This hooks remove the `onChange` & `postState` logic since we only need basic merged state logic. + * Similar to `useState` but prefers the prop `value` when provided. + * Since React 18, unmounted state updates no longer warn, so extra safety guards are unnecessary. + * This hook removes the `onChange` & `postState` logic and keeps a minimal merged-state behavior. */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 62e1415 and eca4dd1.

📒 Files selected for processing (3)
  • src/hooks/useMergedState.ts (1 hunks)
  • src/hooks/usePropState.ts (1 hunks)
  • tests/hooks.test.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/hooks.test.tsx (1)
src/hooks/usePropState.ts (1)
  • usePropState (11-34)
🪛 Biome (2.1.2)
tests/hooks.test.tsx

[error] 465-465: Unexpected empty array pattern.

(lint/correctness/noEmptyPattern)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: test / react component workflow
🔇 Additional comments (13)
tests/hooks.test.tsx (9)

11-11: 导入添加正确

正确导入新的 usePropState 钩子用于测试。


321-339: 测试组件结构符合预期

测试组件正确使用了 usePropState 的新 API,传递 defaultValue ?? null 作为第一个参数,value 作为第二个参数。


341-350: 控制状态转换测试覆盖全面

测试正确验证了从受控状态转为非受控状态时的行为,这是状态管理钩子的关键场景。


352-364: 默认值测试覆盖完整

测试覆盖了字符串和函数两种形式的默认值,确保两种情况都能正常工作。


366-384: 依赖稳定性测试重要

这个测试确保 setVal 函数在依赖数组中时不会引起无限重渲染,这是状态钩子的关键特性。


386-400: React 18 严格模式测试验证了核心改进

测试验证了在 React 18 严格模式下,传递 undefined 作为 value 不会重置为 undefined,这正是新钩子相比旧钩子的改进之处。


402-436: 状态转换测试逻辑正确

测试正确验证了非受控到受控再到非受控的状态转换,包括事件处理和状态更新。


438-459: 受控模式优先级测试重要

测试验证了当提供 value 时,内部状态更新不会影响显示的值,这确保了受控模式的正确性。


461-472: 渲染次数测试验证性能

测试确保钩子只渲染一次,这验证了性能优化的效果。

src/hooks/usePropState.ts (4)

15-16: 初始化逻辑合理(懒初始化保留)

直接将 defaultStateValue 传入 React 的 useState,可在为函数时获得懒初始化语义,符合预期。


17-18: 值合并策略仅以 undefined 判空,请确认与历史语义一致

这里将 null 视作“有值”(受控)且仅把 undefined 视作“空”。若迁移自 useMergedState,请确保测试覆盖 value=null 情形,并确认与既有组件约定一致。

建议补充/验证用例要点:

  • 受控 value=null 时应优先返回 null
  • valuenull/具体值切到 undefined 时的行为(是否重置)符合预期。

28-33: 返回值形态与语义清晰

返回 [mergedValue, setInnerValue] 符合使用预期,便于直接替换 useState


1-2: 确认 useLayoutEffect 已默认导出且签名为 (mount: boolean) => void | VoidFunction,与调用处一致。

Comment on lines 19 to 26
useLayoutEffect(
mount => {
if (!mount) {
setInnerValue(value);
}
},
[value],
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

严重:当 T 为函数类型时,setInnerValue(value) 会被当作 updater 调用,导致状态错误

React setState 将“函数”参数解释为 updater。若 T 本身是函数(少见但可能,例如存储回调),当前实现会把 value 当 updater 执行。应使用函数式 set 包装,以将函数“作为值”写入。同时可顺便避免相等值的无效更新。

 useLayoutEffect( mount => { if (!mount) { - setInnerValue(value); + // 避免函数类型 T 被当作 updater;并跳过相等写入 + setInnerValue(prev => + Object.is(prev, value as T) ? prev : (value as T), + ); } }, [value], );
📝 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
useLayoutEffect(
mount => {
if (!mount) {
setInnerValue(value);
}
},
[value],
);
useLayoutEffect(
mount => {
if (!mount) {
// 避免函数类型 T 被当作 updater;并跳过相等写入
setInnerValue(prev =>
Object.is(prev, value as T) ? prev : (value as T),
);
}
},
[value],
);
🤖 Prompt for AI Agents
In src/hooks/usePropState.ts around lines 19–26, the useLayoutEffect calls setInnerValue(value) which treats a function-typed T as a state updater; change it to call the functional setter and return the incoming value while avoiding unnecessary updates — e.g. setInnerValue(prev => Object.is(prev, value) ? prev : value) — so function values are stored as values and equal values don’t trigger state updates. 
@zombieJ zombieJ merged commit 46832b7 into master Sep 2, 2025
12 checks passed
@zombieJ zombieJ deleted the useMergedState18 branch September 2, 2025 08:48
@afc163
Copy link
Member

afc163 commented Sep 3, 2025

标题没改,不叫 usePropState

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants