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
2 changes: 1 addition & 1 deletion src/packages/toast/doc.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import { Toast } from '@nutui/nutui-react'
| content | Toast content | `React.ReactNode` | `-` |
| duration | Toast duration(s), won't disappear if value is 0 | `number` | `2` |
| position | Vertical position of toast | `top` \| `center` \| `bottom` | `center` |
| title | title | `string` | `-` |
| title | title | `React.ReactNode` | `-` |
| icon | Toast icon | `success` \| `fail` \| `loading` \| `warn` \| `React.ReactNode` | `-` |
| size | Text Size | `small` \| `base` \| `large` | `base` |
| contentClassName | Toast content class name | `string` | `-` |
Expand Down
2 changes: 1 addition & 1 deletion src/packages/toast/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import { Toast } from '@nutui/nutui-react'
| --- | --- | --- | --- |
| content | 消息文本内容 | `React.ReactNode` | `-` |
| duration | 展示时长(秒),值为 0 时,toast 不会自动消失 | `number` | `2` |
| title | 标题 | `string` | `-` |
| title | 标题 | `React.ReactNode` | `-` |
| position | toast展示位置 | `top` \| `center` \| `bottom` | `center` |
| contentClassName | 自定义内容区类名 | `string` | `-` |
| contentStyle | 自定义内容区样式 | `React.CSSProperties` | `-` |
Expand Down
2 changes: 1 addition & 1 deletion src/packages/toast/doc.taro.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import { Toast } from '@nutui/nutui-react-taro'
| --- | --- | --- | --- |
| content | 消息文本内容 | `string` \| `React.ReactNode` | `-` |
| duration | 展示时长(秒),值为 0 时,toast 不会自动消失(loading类型默认为0) | `number` | `2` |
| title | 标题 | `string` | `-` |
| title | 标题 | `React.ReactNode` | `-` |
| position | toast展示位置 | `top` \| `center` \| `bottom` | `center` |
| contentClassName | 自定义内容区类名 | `string` | `-` |
| contentStyle | 自定义内容区样式 | `React.CSSProperties` | `-` |
Expand Down
2 changes: 1 addition & 1 deletion src/packages/toast/doc.zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import { Toast } from '@nutui/nutui-react'
| content | Toast文本內容 | `React.ReactNode` | `-` |
| duration | 展示時長(秒)<br>值為 0 時,toast 不會自動消失 | `number` | `2` |
| position | toast展示位置 | `top` \| `center` \| `bottom` | `center` |
| title | 標題 | `string` | `-` |
| title | 標題 | `React.ReactNode` | `-` |
| icon | 自定義圖標 | `success` \| `fail` \| `loading` \| `warn` \| `React.ReactNode` | `-` |
| size | 文案尺寸,三選一 | `small` \| `base` \| `large` | `base` |
| contentClassName | 自定義內容區類名 | `string` | `-` |
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

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
size: ToastSize
icon: ToastIcon
content: ReactNode
Expand Down
Loading