Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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/packages/formitem/formitem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,13 @@
.nut-form-item-label-right {
justify-content: flex-end;
padding-right: 24px;
white-space: nowrap;
word-wrap: break-word;
}

.nut-form-item-label-left {
position: relative;
padding-left: 12px;
white-space: nowrap;
word-wrap: break-word;
}

.nut-form-item-top {
Expand Down
5 changes: 4 additions & 1 deletion src/packages/toast/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ function show(option: ToastNativeProps | string) {
errorMsg(option)
return notice({ content: option })
}
errorMsg(option.content)
const checkParam = ['title', 'content']
Object.entries(option)
.filter(([k, v]) => checkParam.includes(k))
.forEach(([k, v]) => errorMsg(v))
Comment on lines +65 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

React rendering 0 and false as child elements behavior


Short answer

  • React treats false, true, null and undefined as “ignore me” when used as children — they produce no DOM output. It DOES render numbers and strings, so 0 is rendered as text. [1][2]

Why

  • React’s renderer only skips booleans/null/undefined to support common conditional patterns (e.g. cond && ). Numbers (including 0) are valid renderable children, so 0 will be inserted as text. [1][2]

How to avoid accidentally showing 0

  • Use an explicit boolean expression: {items.length > 0 && }. [2]
  • Use a ternary: {count ? {count} : null} or {count > 0 ? {count} : null}. [3]
  • Coerce to null when you want nothing: {condition ? : null} (preferred). [3]

Examples

  • 0 && // renders "0"
  • false && // renders nothing
  • {count ? {count} : null} // only shows when count != 0. [1][3]

Sources

  • React “False in JSX” tip. [1]
  • Explanation of children and length-check pitfall. [2]
  • Common fixes / patterns (ternary vs &&). [3]

Script executed:

# First, let's examine the toast.tsx file at the specified lines
fd "toast.tsx" -type f | head -20

Repository: jdf2e/nutui-react


Repository: jdf2e/nutui-react
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository jdf2e/nutui-react: Clone operation failed


Script executed:

# Also search for base.ts to check the type changes mentioned
fd "base.ts" -type f | grep -i toast

Repository: jdf2e/nutui-react


Repository: jdf2e/nutui-react
Exit code: 1

stderr:

error: 'ype' isn't a valid value for '--type <filetype>'
  [possible values: file, directory, symlink, executable, empty, socket, pipe]

For more information try '--help'

Script executed:

# List all toast-related files
find . -name "*toast*" -type f 2>/dev/null | head -20

Repository: jdf2e/nutui-react


Repository: jdf2e/nutui-react
Exit code: 0

stdout:

./src/packages/toast/__test__/toast.spec.tsx
./src/packages/toast/toast.scss
./src/packages/toast/toast.taro.tsx
./src/packages/toast/toast.tsx

Script executed:

# Search for errorMsg function definition
rg "errorMsg" -A 5 -B 2

Repository: jdf2e/nutui-react


Repository: jdf2e/nutui-react
Exit code: 0

stdout:

src/sites/sites-react/doc/docs/react/migrate-from-v1.md-- `noButton` 重命名为 `hideFooter`
src/sites/sites-react/doc/docs/react/migrate-from-v1.md-- `onOk` 重命名为 `onConfirm`
src/sites/sites-react/doc/docs/react/migrate-from-v1.md:- `errorMsg` 重命名为 `error`
src/sites/sites-react/doc/docs/react/migrate-from-v1.md-- 移除 `closeOnClickOverlay`,默认支持透传 Popup 属性
src/sites/sites-react/doc/docs/react/migrate-from-v1.md-- `title`、`description`、`tips`、`error` 类型修改为 `ReactNode`
src/sites/sites-react/doc/docs/react/migrate-from-v1.md-- `modelValue` 重命名为 `value`,受控模式
src/sites/sites-react/doc/docs/react/migrate-from-v1.md-- 新增 `onFoucs` 事件
src/sites/sites-react/doc/docs/react/migrate-from-v1.md-
--
src/sites/sites-react/doc/docs/taro/migrate-from-v1.md-- `noButton` 重命名为 `hideFooter`
src/sites/sites-react/doc/docs/taro/migrate-from-v1.md-- `onOk` 重命名为 `onConfirm`
src/sites/sites-react/doc/docs/taro/migrate-from-v1.md:- `errorMsg` 重命名为 `error`
src/sites/sites-react/doc/docs/taro/migrate-from-v1.md-- 移除 `closeOnClickOverlay`,默认支持透传 Popup 属性
src/sites/sites-react/doc/docs/taro/migrate-from-v1.md-- `title`、`description`、`tips`、`error` 类型修改为 `ReactNode`
src/sites/sites-react/doc/docs/taro/migrate-from-v1.md-- `modelValue` 重命名为 `value`,受控模式
src/sites/sites-react/doc/docs/taro/migrate-from-v1.md-- 新增 `onFoucs` 事件
src/sites/sites-react/doc/docs/taro/migrate-from-v1.md-
--
src/packages/toast/toast.tsx-}
src/packages/toast/toast.tsx-
src/packages/toast/toast.tsx:const errorMsg = (msg: any) => {
src/packages/toast/toast.tsx-  if (!msg) {
src/packages/toast/toast.tsx-    console.warn('[NutUI Toast]: msg cannot be null')
src/packages/toast/toast.tsx-  }
src/packages/toast/toast.tsx-}
src/packages/toast/toast.tsx-
src/packages/toast/toast.tsx-function show(option: ToastNativeProps | string) {
src/packages/toast/toast.tsx-  if (typeof option === 'string') {
src/packages/toast/toast.tsx:    errorMsg(option)
src/packages/toast/toast.tsx-    return notice({ content: option })
src/packages/toast/toast.tsx-  }
src/packages/toast/toast.tsx-  const checkParam = ['title', 'content']
src/packages/toast/toast.tsx-  Object.entries(option)
src/packages/toast/toast.tsx-    .filter(([k, v]) => checkParam.includes(k))
src/packages/toast/toast.tsx:    .forEach(([k, v]) => errorMsg(v))
src/packages/toast/toast.tsx-  return notice({
src/packages/toast/toast.tsx-    ...option,
src/packages/toast/toast.tsx-  })
src/packages/toast/toast.tsx-}
src/packages/toast/toast.tsx-
--
src/packages/notify/notify.tsx-}
src/packages/notify/notify.tsx-
src/packages/notify/notify.tsx:const errorMsg = (message: any) => {
src/packages/notify/notify.tsx-  if (!message) {
src/packages/notify/notify.tsx-    console.warn('[NutUI Notify]: message不能为空')
src/packages/notify/notify.tsx-  }
src/packages/notify/notify.tsx-}
src/packages/notify/notify.tsx-
src/packages/notify/notify.tsx-export default {
src/packages/notify/notify.tsx-  text(message: string | React.ReactNode, option = {}) {
src/packages/notify/notify.tsx:    errorMsg(message)
src/packages/notify/notify.tsx-    return notice({ message, type: 'base', ...option })
src/packages/notify/notify.tsx-  },
src/packages/notify/notify.tsx-  hide() {
src/packages/notify/notify.tsx-    if (messageInstance) {
src/packages/notify/notify.tsx-      messageInstance.destroy()
--
packages/nutui-codemod/transforms/rules/props-rules.js-      replacer: 'onConfirm',
packages/nutui-codemod/transforms/rules/props-rules.js-    },
packages/nutui-codemod/transforms/rules/props-rules.js:    errorMsg: {
packages/nutui-codemod/transforms/rules/props-rules.js-      action: 'rename',
packages/nutui-codemod/transforms/rules/props-rules.js-      replacer: 'error',
packages/nutui-codemod/transforms/rules/props-rules.js-    },
packages/nutui-codemod/transforms/rules/props-rules.js-    modelValue: {
packages/nutui-codemod/transforms/rules/props-rules.js-      action: 'rename',

Script executed:

# Search for type definitions in base.ts
rg "title.*ReactNode\|content.*ReactNode" -A 2 -B 2

Repository: jdf2e/nutui-react


Repository: jdf2e/nutui-react
Exit code: 1


Script executed:

# Get the specific lines from toast.tsx
rg "const checkParam = \['title'" -A 5 -B 5

Repository: jdf2e/nutui-react


Repository: jdf2e/nutui-react
Exit code: 0

stdout:

src/packages/toast/toast.tsx-function show(option: ToastNativeProps | string) {
src/packages/toast/toast.tsx-  if (typeof option === 'string') {
src/packages/toast/toast.tsx-    errorMsg(option)
src/packages/toast/toast.tsx-    return notice({ content: option })
src/packages/toast/toast.tsx-  }
src/packages/toast/toast.tsx:  const checkParam = ['title', 'content']
src/packages/toast/toast.tsx-  Object.entries(option)
src/packages/toast/toast.tsx-    .filter(([k, v]) => checkParam.includes(k))
src/packages/toast/toast.tsx-    .forEach(([k, v]) => errorMsg(v))
src/packages/toast/toast.tsx-  return notice({
src/packages/toast/toast.tsx-    ...option,

参数验证逻辑存在缺陷:对 ReactNode 类型的检查过于严格

虽然扩展验证范围到 titlecontent 的思路正确,但 errorMsg 函数使用 !msg 检查存在问题。根据 React 规范,数字 0 是有效的 ReactNode 值(会被渲染为文本),而 falsenullundefined 才是被忽略的值。当前的检查逻辑会将 0 误判为无效。

同时,由于 titlecontent 现在是 ReactNode 类型,警告消息"msg cannot be null"不够准确。

建议改进验证逻辑,准确检查真正需要避免的空值:

const errorMsg = (msg: any) => {
-  if (!msg) {
-    console.warn('[NutUI Toast]: msg cannot be null')
+  if (msg == null || msg === '') {
+    console.warn('[NutUI Toast]: title and content cannot be empty')
   }
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/packages/toast/toast.tsx around lines 65-68, the current validation uses
a falsy check (!msg) which incorrectly treats 0 as invalid; update the
validation to explicitly reject only React-ignored values (null, undefined, and
false) — e.g. check msg === null || msg === undefined || msg === false — and
update the warning text to something like "msg cannot be null, undefined, or
false" to reflect ReactNode types.

return notice({
...option,
})
Expand Down
2 changes: 1 addition & 1 deletion src/types/spec/toast/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type ToastSize = Extract<UISize, 'large' | 'small'> | 'base'
export type BaseToast<OVERLAY_PROPS> = OVERLAY_PROPS & {
id: string
position: VAlign
title: string
title: ReactNode
Comment thread
coderabbitai[bot] marked this conversation as resolved.
size: ToastSize
icon: ToastIcon
content: ReactNode
Expand Down
Loading