react-native-paper
react-native-paper copied to clipboard
Snackbar as a function.
It would be great if we could use Snackbar and toggle it as a function.
Like in Toast provided by Native Base, we can customize it like this
import { Toast } from "native-base";
function show({
text,
type = "danger",
buttonText,
duration = 3000,
position,
bottom = 60,
}) {
Toast.show({
text: text,
position: position,
type: type,
duration: duration,
buttonText: buttonText,
});
}
export default {
show,
};
Then I use it on any screen or component like this
Toast.show({ text: "This API call was successful" }); // This pops a Toast message and disappears after 3 seconds
I love the Snackbar that React Native Paper has, but I couldn't figure out how to use it as described above.
Right now, I have to add a <Snackbar /> component on every page and maintain a state to toggle it. It would have been so good if we could use it like Snackbar.show("Some Text")
If anyone has any advice on using Snackbar as a function to toggle Toasts, I would love to discuss it.
Versions :
- "react-native": "~0.63.4"
- "react-native-paper": "^4.9.2"
Hey! Thanks for opening the issue. The issue doesn't seem to contain a link to a repro (a snack.expo.io link or link to a GitHub repo under your username).
Can you provide a minimal repro which demonstrates the issue? A repro will help us debug the issue faster. Please try to keep the repro as small as possible and make sure that we can run it without additional setup.
Couldn't find version numbers for the following packages in the issue:
-
react-native
-
react-native-paper
-
react-native-vector-icons
Can you update the issue to include version numbers for those packages? The version numbers must match the format 1.2.3.
Couldn't find version numbers for the following packages in the issue:
-
react-native-vector-icons
Can you update the issue to include version numbers for those packages? The version numbers must match the format 1.2.3.
The versions mentioned in the issue for the following packages differ from the latest versions on npm:
-
react-native
(found:0.63.4
, latest:0.64.2
)
Can you verify that the issue still exists after upgrading to the latest versions of these packages?
I created a library for handling snackbar in an easy way. It also supports stacks if you would like to have https://www.npmjs.com/package/react-native-paper-snackbar-stack
Here is my solution, that also works with react-navigation
:
// App.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Provider as PaperProvider } from 'react-native-paper';
import Snackbar from '$common/Snackbar';
import Test from './Test';
const Stack = createNativeStackNavigator();
function App() {
return (
<PaperProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName="TEST">
<Stack.Screen name="TEST" component={TEST} />
</Stack.Navigator>
</NavigationContainer>
<Snackbar.Component /> // <-- Global Snackbar
</PaperProvider>
);
}
export default App;
// $common/Snackbar.tsx
import { useEffect, useState } from 'react';
import { Snackbar as PaperSnackbar } from 'react-native-paper';
import SnackbarManager from './SnackbarManager';
type State = {
visible: boolean;
title?: string;
};
const Snackbar = () => {
const [state, setState] = useState<State>({ visible: false });
useEffect(() => {
SnackbarManager.setListener((title) => setState({ visible: true, title }));
return () => SnackbarManager.setListener(null);
}, []);
return (
<PaperSnackbar visible={state.visible} onDismiss={() => setState({ ...state, visible: false })} duration={6000}>
{state.title}
</PaperSnackbar>
);
};
export default Snackbar;
And I have a global SnackbarManager
:
// SnackbarManager.ts
type Listener = (title: string) => void;
class SnackbarManager {
listener: Listener | null = null;
constructor() {
this.show = this.show.bind(this);
this.setListener = this.setListener.bind(this);
}
setListener(listener: Listener | null): void {
this.listener = listener;
}
show(title: string): void {
this.listener?.(title);
}
}
export default new SnackbarManager();
And finally this is how to trigger it:
import Snackbar from '$common/SnackbarManager';
Snackbar.show("Working!")
Hope this helps someone.
I have used a different approach, in my case, I'm using a snackbar queue context:
import React, {
ComponentPropsWithoutRef,
ReactNode,
createContext,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { IconName } from '@/components/Icon';
interface SnackbarAction {
label: string;
onPress: () => void;
}
interface SnackbarData {
text: string;
action?: SnackbarAction;
icon?: IconName;
onIconPress?: () => void;
duration?: number;
}
type EnqueueSnackbar = (message: SnackbarData | string) => void;
export interface SnackbarController extends SnackbarData {
onDismiss: () => void;
visible: boolean;
}
interface SnackbarProviderProps {
children: ReactNode;
}
interface SnackbarContextData {
queue: SnackbarData[];
enqueue: EnqueueSnackbar;
dequeue: () => void;
clearQueue: () => void;
visible: boolean;
controller: SnackbarController;
}
const SNACKBAR_FADE_TRANSITION_DURATION = 200;
const SnackbarContext = createContext({} as SnackbarContextData);
export const SnackbarProvider = ({ children }: SnackbarProviderProps) => {
const [queue, setQueue] = useState<SnackbarData[]>([]);
const [visible, setVisible] = useState(false);
const dequeueTimer = useRef<NodeJS.Timeout>();
const dequeue = () => {
setVisible(false);
};
const enqueue: EnqueueSnackbar = message => {
const isMessageString = typeof message === 'string';
const newSnackbar: SnackbarData = isMessageString
? {
text: message,
onIconPress: dequeue,
}
: message;
setQueue(prevSnackbars => [...prevSnackbars, newSnackbar]);
};
const clearQueue = () => {
setQueue([]);
};
useEffect(() => {
if (!visible) {
dequeueTimer.current = setTimeout(() => {
setQueue(prevSnackbars => prevSnackbars.slice(1));
}, SNACKBAR_FADE_TRANSITION_DURATION);
return () => {
clearTimeout(dequeueTimer.current);
};
}
}, [visible]);
useEffect(() => {
if (queue.length > 0) {
setVisible(true);
}
}, [queue]);
return (
<SnackbarContext.Provider
value={{
queue,
visible,
enqueue,
dequeue,
clearQueue,
controller: {
...queue[0],
onDismiss: dequeue,
visible,
},
}}
>
{children}
</SnackbarContext.Provider>
);
};
export const useSnackbar = () => useContext(SnackbarContext);
to make it flexible, I also created a custom snackbar component, in which I pass the controller object as props for the component:
import React from 'react';
import { Snackbar as PaperSnackbar } from 'react-native-paper';
import { useSnackbar } from '@/contexts/snackbar';
import { sizes } from '@/styles/sizes';
interface SnackbarProps {
marginBottom?: number;
}
export const Snackbar = ({ marginBottom = sizes.md }: SnackbarProps) => {
const {
controller: { text, ...props },
queue,
} = useSnackbar();
return (
<PaperSnackbar {...props} style={{ marginBottom }}>
{queue.length > 1 ? `(${queue.length}) ${text}` : text}
</PaperSnackbar>
);
};
Snackbar context provider is then placed within paper's provider:
import React, { ReactNode } from 'react';
import { Provider as PaperProvider } from 'react-native-paper';
import { SettingsConsumer, SettingsProvider } from '@/contexts/settings';
import { Settings } from '@/reducers/settings';
import { SnackbarProvider } from './snackbar';
export interface InitialState {
settings: Settings;
}
interface AppProviderProps {
children: ReactNode;
initialState: InitialState;
}
export const AppProvider = ({ children, initialState }: AppProviderProps) => {
const { settings } = initialState;
return (
<SettingsProvider initialState={settings}>
<SettingsConsumer>
{({ theme }) => (
<PaperProvider theme={theme}>
<SnackbarProvider>
{children}
</SnackbarProvider>
</PaperProvider>
)}
</SettingsConsumer>
</SettingsProvider>
);
};
to use it, just place useSnackbar in your component, and call the enqueue function:
import React, { useEffect } from 'react';
//...your imports
import { useSnackbar } from '@contexts/snackbar';
import { Snackbar } from '@components/snackbar';
export const MyComponent = () => {
const snackbar = useSnackbar();
// use case example
const { error } = useAxios();
//...your code here
useEffect(() => {
if (error) {
snackbar.enqueue(error.message);
// or
// snackbar.enqueue({ message: error.message, duration: 5000 });
}
}, [error]);
return (
<>
{/* ...your code here */}
<Snackbar />
</>
)
}
@Tonihm96 @ericmatte have ya'll considered publishing your solutions? @suleymanbariseser are you still maintaining that repo?
@kartikeyvaish please close this is you found a solution.
@tonihm96 @ericmatte have ya'll considered publishing your solutions?
@angelxmoreno good point. As for my solution, it only support one snackbar at the time, so it would required some rework.
Maybe the solution from @tonihm96 is better on that front.