| name | pimcore-studio-ui-notifications-toasts |
| description | Displaying notification messages and toasts in Pimcore Studio UI using the useMessage hook |
| metadata | {"audience":"pimcore-developers","focus":"ui-components-notifications"} |
What This Skill Covers
How to display notification messages (toasts) in Pimcore Studio UI:
- useMessage hook for displaying notifications
- Success messages for completed operations
- Error messages for failures
- Info and warning messages
- Message types and configuration
- Duration and auto-dismiss behavior
- Custom icons and content
When to Use This Skill
Use this when:
- Showing feedback after save/update operations
- Displaying error messages to users
- Confirming successful actions (copy, delete, create)
- Showing warnings or informational messages
- Providing user feedback for async operations
🚨 Import Paths
BEFORE writing any import, read CRITICAL-IMPORT-PATHS.md.
All examples below use bundle imports (@pimcore/studio-ui-bundle/*). For core development (@sdk/*, @Pimcore/*), see the referenced file.
useMessage Hook
The useMessage hook provides a message API for displaying toast notifications.
Basic Usage
import { useMessage } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from 'react-i18next'
export const MyComponent = (): React.JSX.Element => {
const messageApi = useMessage()
const { t } = useTranslation()
const handleSave = async (): Promise<void> => {
try {
await saveData()
await messageApi.success(t('save-success'))
} catch (error) {
await messageApi.error({
content: error.message
})
}
}
return (
<Button onClick={ handleSave }>
{t('save')}
</Button>
)
}
Message API Methods
interface MessageInstance {
success: (content: string | ArgsProps, duration?, onClose?) => MessageType
error: (content: string | ArgsProps, duration?, onClose?) => MessageType
warning: (content: string | ArgsProps, duration?, onClose?) => MessageType
info: (content: string | ArgsProps, duration?, onClose?) => MessageType
loading: (content: string | ArgsProps, duration?, onClose?) => MessageType
open: (config: ArgsProps) => MessageType
}
Success Messages
Show success messages after successful operations.
Simple Success Message
await messageApi.success(t('save-success'))
await messageApi.success(t('save-success'), 5)
await messageApi.success(t('save-success'), 3, () => {
console.log('Message closed')
})
Success Message with Config
messageApi.success({
content: t('operation-completed'),
duration: 3,
onClose: () => {
}
})
Real-World Example: Save Success
import { useMessage } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from 'react-i18next'
export const EditorToolbarSaveButton = (): React.JSX.Element => {
const { t } = useTranslation()
const messageApi = useMessage()
const [saveAsset, { isSuccess }] = useAssetUpdateByIdMutation()
useEffect(() => {
const handleSuccessEvent = async (): Promise<void> => {
if (isSuccess) {
await messageApi.success(t('save-success'))
}
}
handleSuccessEvent().catch((error) => {
console.error(error)
})
}, [isSuccess])
return (
<Button
onClick={ handleSave }
type="primary"
>
{t('save')}
</Button>
)
}
Error Messages
Display error messages when operations fail.
Simple Error Message
await messageApi.error(t('save-failed'))
await messageApi.error({
content: t('save-failed')
})
Error with Exception Details
try {
await saveData()
} catch (error) {
await messageApi.error({
content: error.message || t('unknown-error')
})
}
Real-World Example: Login Error
import { useMessage } from '@pimcore/studio-ui-bundle/components'
export const LoginForm = (): React.JSX.Element => {
const messageApi = useMessage()
const handleAuthentication = async (event: React.FormEvent): Promise<void> => {
try {
event.preventDefault()
const response = await login({ credentials: formState })
if (response.error !== undefined) {
await messageApi.error({
content: t('login-failed')
})
} else {
dispatch(setAuthState(true))
}
} catch (e: any) {
await messageApi.error({
content: e.message
})
}
}
return (
<form onSubmit={ handleAuthentication }>
{/* form fields */}
</form>
)
}
Real-World Example: Auto-Save Failure
useEffect(() => {
if (isError) {
messageApi.error(t('auto-save-failed'))
}
}, [isError])
Warning Messages
Display warning messages for non-critical issues.
Simple Warning
messageApi.warning(t('unsaved-changes'))
messageApi.warning({
content: t('operation-may-take-time'),
duration: 5
})
Warning with Action Needed
const handleDelete = (): void => {
if (hasRelatedItems) {
messageApi.warning({
content: t('item-has-dependencies'),
duration: 0
})
return
}
performDelete()
}
Info Messages
Display informational messages.
Simple Info Message
messageApi.info(t('loading-data'))
messageApi.info({
content: t('background-task-started'),
duration: 3
})
Info with Custom Icon
The useMessage hook automatically adds custom icons for info messages:
messageApi.info(t('processing'))
Loading Messages
Display loading messages for ongoing operations.
Basic Loading Message
const hideLoading = messageApi.loading(t('saving'))
hideLoading()
Loading with Duration
messageApi.loading(t('processing'), 3)
Real-World Pattern: Long Operation
const handleExport = async (): Promise<void> => {
const hide = messageApi.loading(t('exporting-data'), 0)
try {
await performExport()
hide()
await messageApi.success(t('export-complete'))
} catch (error) {
hide()
await messageApi.error(t('export-failed'))
}
}
Advanced Configuration
Message Configuration Options
interface ArgsProps {
content: React.ReactNode
duration?: number
onClose?: VoidFunction
icon?: React.ReactNode
key?: string | number
className?: string
style?: React.CSSProperties
onClick?: (e: React.MouseEvent) => void
}
Custom Duration
messageApi.success(t('saved'), 5)
messageApi.success({
content: t('important-message'),
duration: 0
})
messageApi.success(t('quick-message'), 0.5)
Manual Message Control
const hide = messageApi.success({
content: t('uploading'),
duration: 0
})
setTimeout(() => {
hide()
}, 5000)
Sequential Messages
const handleMultiStepOperation = async (): Promise<void> => {
const loading = messageApi.loading(t('step-1'))
await performStep1()
loading()
const loading2 = messageApi.loading(t('step-2'))
await performStep2()
loading2()
await messageApi.success(t('all-steps-complete'))
}
Common Patterns
Pattern 1: Save Operation Feedback
const [saveData, { isLoading, isSuccess, isError, error }] = useSaveMutation()
const messageApi = useMessage()
const { t } = useTranslation()
useEffect(() => {
if (isSuccess) {
void messageApi.success(t('save-success'))
}
}, [isSuccess])
useEffect(() => {
if (isError) {
void messageApi.error(t('save-failed'))
}
}, [isError])
Pattern 2: Copy/Paste Confirmation
const handleCopy = (element: Element): void => {
setCopiedElement({ element, mode: 'copy' })
void messageApi.success(t('element.tree.copy-success-description', {
type: element.type,
path: element.path
}))
}
const handleCut = (element: Element): void => {
setCopiedElement({ element, mode: 'cut' })
void messageApi.success(t('element.tree.cut-success-description', {
type: element.type,
path: element.path
}))
}
Pattern 3: Data Cleared Confirmation
const handleClearData = async (): Promise<void> => {
clearHotspots()
await messageApi.success(t('hotspots.data-cleared'))
}
Pattern 4: Batch Operation Results
const handleBatchDelete = async (items: Item[]): Promise<void> => {
const loading = messageApi.loading(t('deleting-items', { count: items.length }), 0)
try {
const results = await Promise.allSettled(
items.map(item => deleteItem(item.id))
)
loading()
const succeeded = results.filter(r => r.status === 'fulfilled').length
const failed = results.filter(r => r.status === 'rejected').length
if (failed === 0) {
await messageApi.success(t('batch-delete-success', { count: succeeded }))
} else {
await messageApi.warning(t('batch-delete-partial', {
succeeded,
failed
}))
}
} catch (error) {
loading()
await messageApi.error(t('batch-delete-failed'))
}
}
Pattern 5: Tag Configuration Actions
const handleCreateTag = async (): Promise<void> => {
const result = await createTag(tagData)
if (result.success) {
messageApi.success({
content: t('tag.created', { name: tagData.name }),
duration: 3
})
}
}
const handleUpdateTag = async (): Promise<void> => {
const result = await updateTag(tagData)
if (result.success) {
messageApi.success({
content: t('tag.updated'),
duration: 2
})
}
}
const handleDeleteTag = async (): Promise<void> => {
const result = await deleteTag(tagId)
if (result.success) {
messageApi.success({
content: t('tag.deleted'),
duration: 2
})
}
}
Message Types Comparison
| Type | Use Case | Default Duration | Auto Icon |
|---|
success | Successful operations | 3s | ✓ (checkmark) |
error | Failed operations | 3s | ✓ (error) |
warning | Non-critical issues | 3s | ✓ (warning) |
info | Information | 3s | ✓ (info-circle, custom) |
loading | Ongoing operations | Manual | ✓ (spinner) |
Common Mistakes
❌ Not Using Translation Keys
messageApi.success('Saved successfully')
messageApi.success(t('save-success'))
❌ Forgetting Error Handling
const handleSave = async (): Promise<void> => {
await saveData()
messageApi.success(t('saved'))
}
const handleSave = async (): Promise<void> => {
try {
await saveData()
await messageApi.success(t('saved'))
} catch (error) {
await messageApi.error(t('save-failed'))
}
}
❌ Not Awaiting Messages
messageApi.success(t('saved'))
doNextThing()
await messageApi.success(t('saved'))
doNextThing()
❌ Using console.log Instead of Messages
console.log('Data saved')
messageApi.success(t('save-success'))
❌ Too Many Messages
items.forEach(item => {
messageApi.success(t('item-saved', { name: item.name }))
})
messageApi.success(t('items-saved', { count: items.length }))
❌ Wrong Message Type
if (items.length === 0) {
messageApi.error(t('no-items'))
}
if (items.length === 0) {
messageApi.info(t('no-items'))
}
Best Practices
1. Always Provide User Feedback
const handleAction = async (): Promise<void> => {
try {
await performAction()
await messageApi.success(t('action-complete'))
} catch (error) {
await messageApi.error(t('action-failed'))
}
}
2. Use Appropriate Message Types
messageApi.success(t('saved'))
messageApi.error(t('save-failed'))
messageApi.warning(t('unsaved-changes'))
messageApi.info(t('data-loading'))
messageApi.loading(t('processing'))
3. Keep Messages Concise
messageApi.success(t('saved'))
messageApi.success(t('your-changes-have-been-successfully-saved-to-the-database'))
4. Use Translation Interpolation
messageApi.success(t('item-created', { name: item.name }))
messageApi.warning(t('items-remaining', { count: remaining }))
5. Handle Async Operations Properly
const handleExport = async (): Promise<void> => {
const hide = messageApi.loading(t('exporting'), 0)
try {
const result = await exportData()
hide()
await messageApi.success(t('export-complete'))
} catch (error) {
hide()
await messageApi.error(t('export-failed'))
}
}
Quick Reference
Common Use Cases
await messageApi.success(t('saved'))
await messageApi.error(t('failed'))
messageApi.success(t('saved'), 5)
messageApi.warning({
content: t('warning'),
duration: 0
})
const hide = messageApi.loading(t('loading'), 0)
hide()
messageApi.success(t('created', { name: item.name }))
Next Steps