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
45 changes: 45 additions & 0 deletions docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,51 @@ The misspelled `traileringIcon` props have been renamed:
/>
```

### Snackbar

The message is passed via the `message` prop instead of `children`, and the
`action` prop takes a narrow set of options instead of every `Button` prop:

```diff
<Snackbar
visible={visible}
onDismiss={onDismiss}
+ message="Changes saved"
action={{ label: 'Undo', onPress: () => {} }}
->
- Changes saved
-</Snackbar>
+/>
```

Passing arbitrary nodes as the message is no longer supported, so compose the
message as a string. The `contentStyle` prop was renamed to `messageStyle`, and
it styles the message text rather than a wrapper around arbitrary children.

The Snackbar now renders in a `Portal` on its own, so it overlays all other
content. Wrapping it in a `Portal` is no longer needed — and would render it
twice. Set `portal={false}` to keep it inside the parent instead.
Comment on lines +313 to +315

@satya164 satya164 Oct 22, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wrapping the Snackbar in a Portal does not render it twice. The inner Portal mounts into the same host, and the outer layer stays empty. I checked this with a quick test: <Portal><Snackbar visible ... /></Portal> inside Portal.Host gives one match for the message. The extra wrapper is only redundant.

Suggested change
The Snackbar now renders in a `Portal` on its own, so it overlays all other
content. Wrapping it in a `Portal` is no longer needed — and would render it
twice. Set `portal={false}` to keep it inside the parent instead.
The Snackbar now renders in a `Portal` on its own, so it overlays all other
content, and you no longer need to wrap it in a `Portal`. Set
`portal={false}` to keep it inside the parent instead.

(via Claude, verify if it makes sense)


```diff
-<Portal>
- <Snackbar visible={visible} onDismiss={onDismiss} message="Changes saved" />
-</Portal>
+<Snackbar visible={visible} onDismiss={onDismiss} message="Changes saved" />
```

The action dismisses the Snackbar on its own, so calling `onDismiss` from
`action.onPress` is no longer needed:

```diff
<Snackbar
visible={visible}
onDismiss={onDismiss}
message="Changes saved"
- action={{ label: 'Undo', onPress: () => { doUndo(); onDismiss(); } }}
+ action={{ label: 'Undo', onPress: () => doUndo() }}
/>
```

### TextInput

The Paper 6.x `TextInput` is a complete rewrite with a new API. Import the component the same way, but note that the props and behavior have changed significantly.
Expand Down
8 changes: 3 additions & 5 deletions docs/component-docs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ const pages = {
SegmentedButtons: {
SegmentedButtons: 'SegmentedButtons/SegmentedButtons',
},
Snackbar: 'Snackbar',
Snackbar: {
Snackbar: 'Snackbar/Snackbar',
},
Surface: 'Surface',
Switch: {
Switch: 'Switch/Switch',
Expand Down Expand Up @@ -172,10 +174,6 @@ const componentDocsConfig: ComponentDocsConfig = {
'Comprehensive Portal example':
'https://snack.expo.dev/@react-native-paper/more-examples---comprehensive-portal-example',
},
Snackbar: {
'Snackbar rendered regardless of the parent positioning':
'https://snack.expo.dev/@react-native-paper/more-examples---snackbar-rendered-regardless-of-the-parent-positioning',
},
},
knownIssues: {},
themeColors,
Expand Down
5 changes: 2 additions & 3 deletions example/src/Examples/AppbarExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,9 @@ const AppbarExample = () => {
<Snackbar
visible={showSnackbar}
onDismiss={() => setShowSnackbar(false)}
message="Heading pressed"
duration={Snackbar.DURATION_SHORT}
>
Heading pressed
</Snackbar>
/>
</>
);
};
Expand Down
5 changes: 2 additions & 3 deletions example/src/Examples/ChipExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,9 @@ const ChipExample = () => {
<Snackbar
visible={snackbarProperties.visible}
onDismiss={() => setSnackbarProperties({ visible: false, text: '' })}
message={snackbarProperties.text}
duration={Snackbar.DURATION_SHORT}
>
{snackbarProperties.text}
</Snackbar>
/>
</>
);
};
Expand Down
5 changes: 2 additions & 3 deletions example/src/Examples/SearchbarExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,9 @@ const SearchExample = () => {
<Snackbar
visible={isVisible}
onDismiss={() => setIsVisible(false)}
message="Microphone button pressed"
duration={Snackbar.DURATION_SHORT}
>
Microphone button pressed
</Snackbar>
/>
</>
);
};
Expand Down
73 changes: 31 additions & 42 deletions example/src/Examples/SnackbarExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const SnackbarExample = () => {
showCloseIcon: false,
showLongerMessage: false,
showLongerAction: false,
showInPortal: true,
});

const {
Expand All @@ -27,14 +28,11 @@ const SnackbarExample = () => {
showCloseIcon,
showLongerMessage,
showLongerAction,
showInPortal,
} = options;

const action = {
label: showLongerAction ? 'Toggle Theme' : 'Action',
onPress: () => {
preferences?.toggleTheme();
},
};
const toggleOption = (option: keyof typeof options) =>
setOptions({ ...options, [option]: !options[option] });

return (
<>
Expand All @@ -44,71 +42,65 @@ const SnackbarExample = () => {
<Text>Action button</Text>
<Switch
value={showAction}
onValueChange={() =>
setOptions({ ...options, showAction: !showAction })
}
onValueChange={() => toggleOption('showAction')}
/>
</View>
<View style={styles.row}>
<Text>Close icon button</Text>
<Switch
value={showCloseIcon}
onValueChange={() =>
setOptions({ ...options, showCloseIcon: !showCloseIcon })
}
onValueChange={() => toggleOption('showCloseIcon')}
/>
</View>
<View style={styles.row}>
<Text>Longer message</Text>
<Switch
value={showLongerMessage}
onValueChange={() =>
setOptions({
...options,
showLongerMessage: !showLongerMessage,
})
}
onValueChange={() => toggleOption('showLongerMessage')}
/>
</View>
<View style={styles.row}>
<Text>Longer action</Text>
<Switch
value={showLongerAction}
onValueChange={() =>
setOptions({
...options,
showLongerAction: !showLongerAction,
})
}
onValueChange={() => toggleOption('showLongerAction')}
/>
</View>
<View style={styles.row}>
<Text>Render in portal</Text>
<Switch
value={showInPortal}
onValueChange={() => toggleOption('showInPortal')}
/>
</View>
</List.Section>

<View style={styles.wrapper}>
<Button
mode="outlined"
onPress={() =>
setOptions({ ...options, showSnackbar: !showSnackbar })
}
>
<Button mode="outlined" onPress={() => toggleOption('showSnackbar')}>
{showSnackbar ? 'Hide' : 'Show'}
</Button>
</View>
</ScreenWrapper>
<Snackbar
visible={showSnackbar}
onDismiss={() => setOptions({ ...options, showSnackbar: false })}
action={showAction ? action : undefined}
onIconPress={
showCloseIcon
? () => setOptions({ ...options, showSnackbar: false })
onDismiss={() => toggleOption('showSnackbar')}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

onDismiss toggles showSnackbar instead of setting it to false. Any second onDismiss call shows the Snackbar again. The double-click case in the Snackbar comment does exactly that. The timer also calls the onDismiss from the render that finished the enter animation, so toggleOption spreads stale options and reverts switches flipped while the Snackbar is visible. A functional update that sets false avoids both:

Suggested change
onDismiss={() => toggleOption('showSnackbar')}
onDismiss={() =>
setOptions((prev) => ({ ...prev, showSnackbar: false }))
}

The same applies to onIconPress below. (via Claude, verify if it makes sense)

message={showLongerMessage ? LONG_MESSAGE : SHORT_MESSAGE}
action={
showAction
? {
label: showLongerAction ? 'Toggle theme' : 'Action',
onPress: () => {
preferences?.toggleTheme();
},
}
: undefined
}
onIconPress={
showCloseIcon ? () => toggleOption('showSnackbar') : undefined
}
portal={showInPortal}
duration={Snackbar.DURATION_MEDIUM}
style={showLongerAction && styles.longerAction}
>
{showLongerMessage ? LONG_MESSAGE : SHORT_MESSAGE}
</Snackbar>
/>
</>
);
};
Expand All @@ -130,9 +122,6 @@ const styles = StyleSheet.create({
paddingVertical: 8,
paddingHorizontal: 16,
},
longerAction: {
flexDirection: 'column',
},
});

export default SnackbarExample;
Loading
Loading