How to stop a global component being rendered on a particular screen? - reactjs

I'm using react-native-toast-notifications package for showing in-app notifications.
I'm trying to integrate it for showing notifications when a chat arrives. But this notification is also being displayed on the chat room screen where the one-to-one chat happens.
How can I prevent the notification being appeared on the chat room screen?
This is my provider component
<ToastProvider
swipeEnabled={true}
placement="top"
duration={5000}
animationType="zoom-in"
animationDuration={300}
renderType={{
message_toast: (toast) => <ToastContainer toast={toast} />,
}}
>
<SymbolsProvider>
<Navigation />
</SymbolsProvider>
</ToastProvider>
I'm using the useToast() hook provided by the package to show notifications
const toast = useToast();
toast.show(socketMessage.username + " Sent a message", {
position: "bottom",
duration: 3000,
style: toastStyle,
type: "success",
});
Sent a message
This notification is appearing on the chat room screen also which should not be happening.

Here's an example base on the comment under the question:
import { ToastProvider } from 'react-native-toast-notifications'
import {NavigationContainer} from '#react-navigation/native'
import Screens from './screens/index'
export default function App() {
return (
<ToastProvider>
<NavigationContainer>
<Screens />
</NavigationContainer>
</ToastProvider>
);
}
import React from 'react';
import {
SafeAreaView,
View,
StyleSheet,
TouchableOpacity,
Text,
} from 'react-native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { useNavigation } from '#react-navigation/native';
import { useToast } from 'react-native-toast-notifications';
const Stack = createNativeStackNavigator();
const Drawer = createDrawerNavigator();
const ToastButton = ({ style}) => {
const toast = useToast();
const navigation = useNavigation();
const navState = navigation.getState();
console.log(navState.routeNames);
const currentRoute = navState.routeNames[navState.index];
const onPress = () => {
if (currentRoute != 'Chat') toast.show('Toast from ' + currentRoute);
};
return (
<TouchableOpacity style={[styles.container, style]} onPress={onPress}>
<Text>Show Toast</Text>
</TouchableOpacity>
);
};
const HomeScreen = (props) => {
return (
<View style={styles.flex}>
<ToastButton />
</View>
);
};
const ChatScreen = (props) => {
return (
<View style={styles.flex}>
<ToastButton />
</View>
);
};
export default function ScreenIndex(props) {
return (
<SafeAreaView style={{ flex: 1 }}>
<Drawer.Navigator useLegacyImplementation={true}>
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Chat" component={ChatScreen} />
</Drawer.Navigator>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
flex: {
flex: 1,
backgroundColor: '#eef',
},
});

Related

Confusion with React Native Navigation

I'm building a React Native gig guide app, using stack navigation to get around the different screens.
So here's what I want to happen - the user can navigate from Map.js to List.js, where they're presented with a list of gigs. I want them to be able to tap on a gig and be directed to GigDetails.js, where they can see the details about that specific gig.
Users also have the option of tapping on a gig marker in Map.js which also directs them to GigDetails.js. How do I configure my routing so users can get to GigDetails from both Map.js and List.js?
Currently, Map-to-details is working, but I don't know how to go from List-to-details.
App structure is as follows (route with asterisk is the part I'm having trouble with:
screens/List.js <-----> screens/Map.js (Home screen)
*| |
*| | (GigDetails navigated to via map Marker Callout)
*| |
*GigDetails.js GigDetails.js
Code:
App.js
import 'react-native-gesture-handler';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { MyStack } from './routes/homeStack';
export default function App() {
return (
<NavigationContainer>
<MyStack/>
</NavigationContainer>
);
}
HomeStack.js
import { createStackNavigator } from "#react-navigation/stack";
import List from '../screens/List'
import Map from '../screens/Map'
import Header from "../components/Header";
import GigDetails from "../screens/GigDetails";
const Stack = createStackNavigator()
export const MyStack = () => {
return (
<Stack.Navigator
initialRouteName="Map"
>
<Stack.Screen
name="Map"
component={Map}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
<Stack.Screen
name="List"
component={List}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
<Stack.Screen
name="GigDetails"
component={GigDetails}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
</Stack.Navigator>
);
};
Map.js
import { StyleSheet,View,Text,Pressable } from 'react-native'
import GigMap from '../components/GigMap'
const Map = ({ navigation }) => {
return (
<View style = {styles.container}>
<GigMap navigation = {navigation}/>
<View style = {styles.footer}>
<Pressable
title = "Go to list view"
onPress = {() => navigation.navigate("List")}
style = {styles.button}
>
<Text style = {styles.buttonText}>List View</Text>
</Pressable>
</View>
</View>
)
}
GigMap.js
import { useState,useEffect } from 'react';
import { StyleSheet, Text, View,Pressable } from 'react-native';
import MapView from 'react-native-maps';
import { Marker,Callout } from 'react-native-maps';
import { query,collection,getDocs } from 'firebase/firestore';
import { db } from '../firebase';
import CalloutView from './CalloutView';
import { mapStyle } from '../util/mapStyle';
import dayjs from 'dayjs';
const GigMap = ({ navigation }) => {
const [gigs, setGigs] = useState([]);
const [ date,setDate ] = useState(dateToday)
const [ daysAdded,setDaysAdded ] = useState(1)
//Generating current date
const addHours =(numOfHours, date = new Date()) => {
date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000);
return date;
}
let localDate = addHours(13)
useEffect(() => {
setDate(localDate)
},[])
const addDay = () => {
setDaysAdded(daysAdded+1)
localDate.setDate(localDate.getDate() + daysAdded)
setDate(localDate)
}
console.log(date)
const day = new Date().getDate();
const month = new Date().getMonth() + 1;
const year = new Date().getFullYear();
const dateToday = `${day}/${month}/${year}`;
//Making call to Firebase to retrieve gig documents from 'gigs' collection
useEffect(() => {
const getGigs = async () => {
try {
const gigArray = [];
const q = query(collection(db, "gigs"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) =>
gigArray.push({ id: doc.id, ...doc.data() })
);
setGigs(gigArray);
} catch (err) {
console.log(`Error: ${err}`);
}
};
getGigs();
}, []);
//Filtering through gigs to return only current day's gigs
const gigsToday = gigs.filter((gig) => gig.date === dateToday);
return (
<View style={styles.container}>
<Text style={styles.headerText}>Today's gigs</Text>
<MapView
initialRegion={{
latitude: -41.29416,
longitude: 174.77782,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}
style={styles.map}
customMapStyle={mapStyle}
>
{gigsToday.map((gig, i) => (
<Marker
key={i}
coordinate={{
latitude: gig.location.latitude,
longitude: gig.location.longitude,
}}
image={require("../assets/Icon_Gold_48x48.png")}
>
<Callout
style={styles.callout}
onPress={() =>
navigation.navigate("GigDetails", {
venue: gig.venue,
date: gig.date,
gigName: gig.gigName,
time: gig.time,
})
}
>
<CalloutView
venue={gig.venue}
date={gig.date}
gigName={gig.gigName}
time={gig.time}
style={styles.calloutView}
/>
</Callout>
</Marker>
))}
</MapView>
<View style = {styles.buttonOptions}>
<Pressable >
<Text style = {styles.buttonOptionsText}>previous day's gigs</Text>
</Pressable>
<Pressable onPress = {addDay}>
<Text style = {styles.buttonOptionsText}>next day's gigs</Text>
</Pressable>
</View>
</View>
);
};
List.js
import { View,Text } from 'react-native'
import ListByDay from '../components/ListByDay';
const List = () => {
return (
<View>
<ListByDay/>
</View>
)
}
export default List;
ListByDay.js
import { StyleSheet, Text, View,FlatList,TouchableOpacity } from 'react-native';
import { useGigs } from '../hooks/useGigs';
const ListByDay = ({ navigation }) => {
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
test: {
borderWidth:1,
borderColor:'black',
},
header: {
padding:10
}
})
export default ListByDay;
The component ListByDay currently tries to destructure the navigation object from its props. However, the navigation object is passed by the react-navigation framework to the components that are defined as screens inside a navigator. This is not the case for ListByDay. Thus, you cannot access the navigation object as you are currently trying to do.
There are multiple options.
1) Pass the navigation object as a prop from its parent which is a screen inside a navigator
Since List is the parent component for ListByDay and List is an actual screen defined in your stack navigator, it will receive the navigation object as a prop from the react-navigation framework. You can access it and pass it to its children.
const List = ({navigation}) => {
return (
<View>
<ListByDay navigation={navigation} />
</View>
)
}
Then, implement the navigation logic in the onPress function of your TouchableOpacity inside ListByDay.
const ListByDay = ({ navigation }) => {
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test} onPress={() => navigation.navigate(...)}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
2) Use the useNavigation hook
If you do not want to pass the navigation object down to ListByDay, then you can always use the useNavigation hook instead.
const ListByDay = () => {
const navigation = useNavigation()
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test} onPress={() => navigation.navigate(...)}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}

onPress function for DrawerNavigator

I have created a DrawerNavigator in my react native app which looks like this.
I just dont like the default header that react- native gives. So I wanna access it through an icon. I guess also using the onPress condition
import { createStackNavigator } from '#react-navigation/stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
// importing of all screens
const Drawer = createDrawerNavigator();
const DrawerContent = () => {
return (
<Drawer.Navigator>
<Drawer.Screen
name="Home"
component={CategoryStack}
/>
<Drawer.Screen name="Aboutus" component={Aboutus} />
<Drawer.Screen name="Interest Recieved" component={InterestRecieved} />
</Drawer.Navigator>
);
};
const Stack = createStackNavigator();
const MainStack = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="Loading"
component={Loading}
options={{ headerShown: false }}
/>
</Stack.Navigator>
);
};
export default MainStack;
How do I open it using an onPress of an icon?
Thanks!
React navigation useNavigation hook expose drawer actions - toggleDrawer,openDrawer and closeDrawer event handlers which you can use to open or close drawer.
import React from "react";
import { View, Text, StyleSheet, Pressable } from "react-native";
import { useNavigation } from "#react-navigation/native";
const ToggleDrawer = () => {
const { toggleDrawer,closeDrawer,openDrawer } = useNavigation();
return (
<Pressable onPress={toggleDrawer}>{/** Add your Icon Here */}</Pressable>
);
};
You can check in-depth drawer example
In the header options you can customize the header and add for example an icon on the top left side like this.
useLayoutEffect(() => {
navigation.setOptions({
title: 'ScreenName',
headerLeft: () => (
<View style={{ marginLeft: 15 }}>
<TouchableOpacity onPress={() => navigation.openDrawer()} >
{*INSERT ICON HERE*}
</TouchableOpacity>
</View>
),
})
})

EXPO: Setting up react-native-action-sheet

Currently trying to setup react-native-action-sheet and getting an invalid hook call
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I followed the example and wrapped my wrapped your top-level component with even when using hooks.
export default () => (
<ActionSheetProvider>>
<App />
</<ActionSheetProvider>>
);
Wondering if it's the way I set this up:
import { Linking } from 'react-native';
import { useActionSheet } from '#expo/react-native-action-sheet';
export const SideButton = () => {
const { showActionSheetWithOptions } = useActionSheet();
const cancelButtonIndex = 1;
const options = ['Email', 'Cancel'];
const title = 'Email Me';
const message = 'Let me know your issues';
return showActionSheetWithOptions(
{
options,
cancelButtonIndex,
title,
message,
},
(buttonIndex) => {
if (buttonIndex === 0) {
Linking.openURL('mailto:_____').catch();
} else {
return;
}
}
);
};
Or even how I call it here:
import { Linking, Text, TouchableOpacity, View } from 'react-native';
import { SideButton } from './utils/HelpPopUp';
const ButtonContainer = () => (
<TouchableOpacity>
<Text onPress={() => Linking.openURL('_MY_WEBSITE_').catch()}>Checkout my stuff</Text>
</TouchableOpacity>
);
const Menu = (props) => {
return (
<View>
<ButtonContainer />
</View>
);
};
export default Menu;
Sorry, my answer would be to suggest an alternative rather than answer your question and present the component I use myself. I did not use the package you mentioned in the topic, but I tried with a different package before, it does the same job. https://www.npmjs.com/package/react-native-actions-sheet
This is the custom component I used.
import React from 'react';
import {StyleSheet, Text, View} from 'react-native';
import ActionSheet from 'react-native-actions-sheet';;
const CustomActionSheet = React.forwardRef(({children, title}, ref) => {
return (
<ActionSheet
ref={ref}
headerAlwaysVisible={true}
containerStyle={[
styles.containerStyle,
{backgroundColor: '#FFF'},
]}
CustomHeaderComponent={
<View style={[styles.header, {backgroundColor: '#4ac'}]}>
<Text style={styles.title}>
{title}
</Text>
</View>
}>
{children}
</ActionSheet>
);
});
const styles = StyleSheet.create({
header: {
height: 50,
justifyContent: 'center',
padding: 5,
},
title: {
color: '#FFF',
fontSize: globalStyles.titleText.fontSize,
},
containerStyle: {
borderRadius: 0,
},
});
export default CustomActionSheet;
Usage:
import React, { createRef } from "react";
import {View, Text} from "react-native";
import CustomActionSheet from './CustomActionSheet';
const actionSheetRef = createRef();
const AnythingPage = () => {
return (
<View>
<Text onPress={() => actionSheetRef.current?.show()}>
Open Custom Sheet
</Text>
<CustomActionSheet ref={actionSheetRef} title={'Title'}>
<View>
<Text>Add anything in it.</Text>
</View>
</CustomActionSheet>
</View>
)
}
You can develop a structure that will be used in the whole application by going through these.

How to show ActivityIndicator at the time of data loading in React Native

I am fetching data from the api in flatlist I want to show ActivityIndicator at the time of data loading.
I have implemented code to do that but its not showing ActivityIndicator.
Below is my code:
App.js
import React from 'react';
import {StatusBar, StyleSheet} from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import List from './components/list';
import Detail from './components/details';
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<StatusBar barStyle="dark-content" backgroundColor="#8E24AA"/>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={List}
options={{
headerStyle: {
backgroundColor: '#AB47BC',
},
headerTintColor: '#fff' }} />
<Stack.Screen
name="Detail"
component={Detail}
options={{
headerStyle: {
backgroundColor: '#AB47BC',
},
headerTintColor: '#fff' }} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default App;
List.js
import React, {useEffect,useState} from 'react';
import {View,Text,StyleSheet,ActivityIndicator,FlatList} from 'react-native';
const List = () => {
const[post,setPost] = useState([]);
const[isLoading,setLoading] = useState(true);
useEffect(() => {
const url = 'http://api.duckduckgo.com/?q=simpsons+characters&format=json';
fetch(url).then((res) => res.json())
.then((resp) => {
setPost(resp.RelatedTopics);
setLoading(false);
}).catch((err) => alert(err));
},[]);
return(
<View style={{flex:1}}>
{ isLoading ? <ActivityIndicator/> : <FlatList
data = {post}
keyExtractor = {(item) => item.FirstURL}
renderItem = {({item}) => <Text style={styles.my}>{item.Text.split('-', 1)[0]}</Text>}/>
}
</View>
);
};
const styles = StyleSheet.create({
my:{
marginBottom: 15,
marginTop: 15,
marginLeft: 15
}
});
export default List;
Someone let me know what I am doing wrong in above code.
You are never setting isLoading to false, you can do this when the data is fetched (in the then block)
useEffect(() => {
const url = 'http://api.duckduckgo.com/?q=simpsons+characters&format=json';
fetch(url).then((res) => res.json())
.then((resp) => {
setPost(resp.RelatedTopics);
setLoading(false); // loaded
})
.catch((err) => alert(err));
}, []);
This way, when the data is loaded, the state will update causing the component to re-render.
<View style={{flex:1}}>
{ isLoading ? <ActivityIndicator color="#000"/> : <FlatList
data = {post}
keyExtractor = {(item) => item.FirstURL}
renderItem = {({item}) => <Text style={styles.my}>{item.Text.split('-', 1)[0]}</Text>}/>
}
</View>
Apply the flex:1 to cover entire screen.

Dynamically update context in React Native hook

I am trying to update theme of my react native app using context API but it is throwing an error setThemeMode is not a function. (In 'setThemeMode(themeMode === 'light' ? 'dark': 'light')', 'setThemeMode' is "i")
I have taken refernce of following blog article
https://www.smashingmagazine.com/2020/01/introduction-react-context-api/
Main Error Image
ThemeContext.js
import React from 'react';
const ThemeContext = React.createContext(['light', () => {}]);
export default ThemeContext;
App.js
import React, {useState} from 'react';
import Nav from './src/navigation/Nav';
import 'react-native-gesture-handler';
import ThemeContext from './src/context/ThemeContext';
const App = () => {
const [theme] = useState("light");
return (
<>
<ThemeContext.Provider value={theme}>
<Nav />
</ThemeContext.Provider>
</>
);
};
export default App;
Settings.js
import React, {useContext} from 'react';
import {View, Text, TouchableHighlight, Alert} from 'react-native';
import Icon from 'react-native-vector-icons/dist/Ionicons';
import Switches from 'react-native-switches';
import ThemeContext from './../context/ThemeContext';
import AppTheme from './../Colors';
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp,
} from 'react-native-responsive-screen';
import ThemeSwitch from './ThemeSwitch';
const Settings = () => {
const [themeMode, setThemeMode] = useContext(ThemeContext);
const theme = useContext(ThemeContext);
const currentTheme = AppTheme[theme];
return (
<>
<TouchableHighlight
onPress={() => setThemeMode(themeMode === 'light' ? 'dark' : 'light')}
style={{
backgroundColor: 'black',
borderRadius: 100,
width: wp(14),
height: wp(14),
justifyContent: 'center',
alignItems: 'center',
}}>
<Icon name="md-arrow-round-back" size={wp(8)} color="white" />
</TouchableHighlight>
</>
);
};
export default Settings;
Nav.js
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import Welcome from './../components/Welcome';
import Settings from './../components/Settings';
import Main from './../components/Main';
const Stack = createStackNavigator();
const Nav = () => {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="Main" component={Main} />
<Stack.Screen name="Settings" component={Settings} />
<Stack.Screen name="Welcome" component={Welcome} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default Nav;
Colors.js
const AppTheme = {
light: {
name: 'light',
textColor: 'black',
backgroundColor: 'white',
},
dark: {
name: 'dark',
textColor: 'white',
backgroundColor: 'black',
},
};
export default AppTheme;
I want to dynamically update context. Pardon me for such silly bug but I am new to react and Js.
I have attached the issue image. I think I am doing something wrong with useContext because when I try to console.log(ThemeContext) it was showing undefined instead of light.
In App js ... You have to set the theme mode like
const [themeMode, setThemeMode] = useState('light');
then
<ThemeContext.Provider value={themeMode,setThemeMode}>
then wherever you want to update the value ... you can access it
const [theme,setThemeMode] = useContext(ThemeContext)
instead of create and assign state to context use the state from Context
const [themeMode, setThemeMode] = useContext(ThemeContext);
Should be
const [themeMode, setThemeMode] = useState(ThemeContext);

Resources