I've been facing a problem for 1 week. My question is I would like to show up new message badge without clicking message room when I run my app I can show the badge after clicking message room.
when I run my app. Home page.
and then I clicked Message room Now I can see the badge
ChatList component src/Components/ChatList
const [badgeCount, setBadgeCount] = useState(0);
const {badgeStateDispatch} = useContext(GlobalContext);
const unReadMessageQuery = db
.collection('chats')
.doc(id)
.collection('messages')
.where('user', '==', recipientEmail)
.where('unread', '==', true);
const [unReadMessageToBadge] = useCollection(unReadMessageQuery);
useEffect(() => {
const badgeMessagesCount = unReadMessageToBadge?.size;
console.log(badgeMessagesCount);
setBadgeCount(badgeMessagesCount);
badgeStateDispatch({type: BADGE_SHOW});
}, [unReadMessageToBadge]);
reducer
import {BADGE_SHOW, BADGE_RESET} from '../../Constants/actionTypes';
const badgeCount = (state, {type, payload}) => {
switch (type) {
case BADGE_SHOW:
return {...state, badge: (state.badge = true)};
case BADGE_RESET:
return {...state, badge: (state.badge = null)};
default:
return state;
}
};
export default badgeCount;
TabNavi src/Navigator/Tabnavi.js
import React, {useContext} from 'react';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import HomeStackNavi from './HomeStackNavi';
import ProfileStackNavi from './ProfileStackNavi';
import MessageStackNavi from './MessageStackNavi';
import Entypo from 'react-native-vector-icons/Entypo';
import Icon from 'react-native-vector-icons/FontAwesome';
import colors from '../Assets/theme/colors';
import {GlobalContext} from '../Context/GlobalProvider';
const Tab = createBottomTabNavigator();
const TabNavi = () => {
const {
badgeState: {badge},
} = useContext(GlobalContext);
return (
<Tab.Navigator
tabBarOptions={{
activeTintColor: colors.white,
inactiveTintColor: colors.gray,
style: {backgroundColor: colors.primary},
}}>
<Tab.Screen
name="Home"
component={HomeStackNavi}
options={{
tabBarIcon: () => <Icon name="home" size={25} color={colors.white} />,
}}
/>
<Tab.Screen
name="Messages"
component={MessageStackNavi}
options={{
tabBarIcon: () => (
<Entypo name="message" size={25} color={colors.white} />
),
tabBarBadge: badge,
tabBarBadgeStyle: {
top: Platform.OS === 'ios' ? 0 : 9,
minWidth: 10,
maxHeight: 10,
borderRadius: 7,
fontSize: 10,
},
}}
/>
<Tab.Screen
name="Profile"
component={ProfileStackNavi}
options={{
tabBarIcon: () => <Icon name="user" size={25} color={colors.white} />,
}}
/>
</Tab.Navigator>
);
};
export default TabNavi;
Edit: This is the parent component of the ChatList.
const ChatRoomList = () => {
const [user] = useAuthState(auth);
if (!user || !user.email) return;
const {badgeStateDispatch} = useContext(GlobalContext);
const renderItem = useCallback(({item}) => <ChatList item={item} />, []);
const keyExtractor = useCallback(item => item.id, []);
const userChatRef = db
.collection('chats')
.where('users', 'array-contains', user.email);
const [chatsSnapshot] = useCollection(userChatRef);
const [chatList, setChatList] = useState([]);
useEffect(() => {
const chatData = new Array();
chatsSnapshot?.docs.map(chat => {
chatData.push({id: chat.id, users: chat.data().users});
});
setChatList(chatData);
}, [chatsSnapshot]);
useFocusEffect(
React.useCallback(() => {
badgeStateDispatch({type: BADGE_RESET});
}, []),
);
return (
<View>
<FlatList
data={chatList}
renderItem={renderItem}
keyExtractor={keyExtractor}
initialNumToRender={7}
showsVerticalScrollIndicator={false}
/>
</View>
);
};
export default ChatRoomList;
I'd really appreciate your help.
Your issue is that your call to check for unread messages is in your ChatList component which is in your second tab. react-navigation only renders that component when you navigate to the second tab so therefore this code is only running when you select that tab.
You must move this call elsewhere, either into a higher order component or out of the React tree altogether, although this would require a bit of reworking as you're relying on hooks.
Related
I have implemented AWS custom UI authenctication in my react native app and React navigation to navigate through the different screens.
While implementing the logical conditions to see if the "User is already logged in or not" I have assigned the screen "Home" for logged in user and screen 'Login' for not logged in user it's working fine and navigating as expected but the console is showing this error when clicking on login button.
ERROR The action 'NAVIGATE' with payload {"name":"Home"} was not handled by any navigator.
Here is the Navigation code:
import React, {useEffect, useState} from 'react'
import {ActivityIndicator, Alert, View} from 'react-native';
import HomeScreen from '../screens/HomeScreen';
import LoginScreen from '../screens/LoginScreen';
import RegisterScreen from '../screens/RegisterScreen';
import ConfirmEmailScreen from '../screens/ConfirmEmailScreen';
import ForgotPassword from '../screens/ForgotPassword';
import NewPasswordScreen from '../screens/NewPasswordScreen';
import { NavigationContainer } from '#react-navigation/native'
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import {Auth, Hub} from 'aws-amplify';
const Stack = createNativeStackNavigator();
const Navigation = () => {
const [user, setUser] = useState(undefined);
//Checking if user is already logged in or not!
const checkUser = async () => {
try {
const authUser = await Auth.currentAuthenticatedUser({bypassCache: true});
setUser(authUser);
} catch(e) {
setUser(null);
}
};
useEffect(() => {
checkUser();
}, []);
useEffect(() => {
const listener = data => {
if (data.payload.event === 'signIn' || data.payload.event === 'signOut') {
checkUser();
}
}
Hub.listen('auth', listener);
return () => Hub.remove('auth', listener);
}, []);
if (user === undefined) {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator/>
</View>
);
}
return (
<NavigationContainer>
<Stack.Navigator>
{/*{If user is logged in navigate him to Homescreen else go throght the Screens based on the user selection */}
{user ? (
<Stack.Screen name='Home' component={HomeScreen}/>
) : (
<>
<Stack.Screen name='Login' component={LoginScreen}/>
<Stack.Screen name='Register' component={RegisterScreen}/>
<Stack.Screen name='ConfirmEmail' component={ConfirmEmailScreen}/>
<Stack.Screen name='ForgotPassword' component={ForgotPassword}/>
<Stack.Screen name='NewPassword' component={NewPasswordScreen}/>
</>
)}
</Stack.Navigator>
</NavigationContainer>
);
};
export default Navigation;
Here is the Login Screen:
import React, {useState} from 'react'
import { View, Text, Image, StyleSheet, useWindowDimensions, TouchableWithoutFeedback, Keyboard, Alert, KeyboardAvoidingView, ScrollView } from 'react-native'
import Logo from '../../../assets/images/logo-main.png'
import CustomButton from '../../components/CustomButton/CustomButton';
import CustomInput from '../../components/CustomInput/CustomInput';
import { useNavigation } from '#react-navigation/native';
import {Auth} from 'aws-amplify';
import {useForm} from 'react-hook-form';
const LoginScreen = () => {
const [loading, setLoading] = useState(false);
const {height} = useWindowDimensions();
const {control, handleSubmit, formState: {errors}} = useForm();
const navigation = useNavigation();
const onLoginPressed = async (data) => {
if(loading) {
return;
}
setLoading(true);
try {
await Auth.signIn(data.username, data.password);
navigation.navigate('Home');
} catch(e) {
Alert.alert('Opps', e.message)
}
setLoading(false);
};
const onForgotPasswordPressed = () => {
navigation.navigate('ForgotPassword');
}
const onRegisterPressed = () => {
navigation.navigate('Register')
}
return (
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={styles.container}>
<ScrollView contentContainerStyle={{flexGrow:1, justifyContent:'center'}} showsVerticalScrollIndicator={false}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.root}>
<Image source={Logo} style={[styles.logo, {height : height * 0.2}]} resizeMode={'contain'} />
<CustomInput icon='user' name='username' placeholder='Username' control={control} rules={{required: 'Username is required'}} />
<CustomInput icon='lock' name='password' placeholder='Password' control={control} rules={{required: 'Password is required'}} secureTextEntry={true} />
<CustomButton text={loading ? 'Loading...' : 'Login Account'} onPress={handleSubmit(onLoginPressed)} />
<CustomButton text='Forgot Password?' onPress={onForgotPasswordPressed} type='TERTIARY' />
<CustomButton text="Don't have an account? Create one" onPress={onRegisterPressed} type='TERTIARY' />
</View>
</TouchableWithoutFeedback>
</ScrollView>
</KeyboardAvoidingView>
);
};
const styles = StyleSheet.create({
root: {
alignItems: 'center',
padding: 20,
},
logo: {
width: 200,
maxWidth: 300,
maxHeight: 300,
},
});
export default LoginScreen;
Issue
At the time you are calling navigation.navigate('Home') there is no screen called in Home in Navigation because of that ternary that checks whether there is a user or not and renders conditionally your screens. That's why React Naviagation is not happy.
Solution
React Navigation's documention tell us that we shouldn't "manually navigate when conditionally rendering screens". Means you should find a way to call setUser(user) in Login where setUser is the state setter from Navigation component.
To do so we can use a context and for that change Navigation as follow (I added comments where I changed things):
import React, { createContext, useEffect, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import ConfirmEmailScreen from "../screens/ConfirmEmailScreen";
import ForgotPassword from "../screens/ForgotPassword";
import HomeScreen from "../screens/HomeScreen";
import LoginScreen from "../screens/LoginScreen";
import NewPasswordScreen from "../screens/NewPasswordScreen";
import RegisterScreen from "../screens/RegisterScreen";
import { NavigationContainer } from "#react-navigation/native";
import { createNativeStackNavigator } from "#react-navigation/native-stack";
import { Auth, Hub } from "aws-amplify";
const Stack = createNativeStackNavigator();
// Line I added
export const AuthContext = createContext(null);
const Navigation = () => {
const [user, setUser] = useState(undefined);
//Checking if user is already logged in or not!
const checkUser = async () => {
try {
const authUser = await Auth.currentAuthenticatedUser({ bypassCache: true });
setUser(authUser);
} catch (e) {
setUser(null);
}
};
useEffect(() => {
checkUser();
}, []);
useEffect(() => {
const listener = (data) => {
if (data.payload.event === "signIn" || data.payload.event === "signOut") {
checkUser();
}
};
Hub.listen("auth", listener);
return () => Hub.remove("auth", listener);
}, []);
if (user === undefined) {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator />
</View>
);
}
return (
// Wrapper I added
<AuthContext.Provider value={{ user, setUser }}>
<NavigationContainer>
<Stack.Navigator>
{/*{If user is logged in navigate him to Homescreen else go throght the Screens based on the user selection */}
{user ? (
<Stack.Screen name="Home" component={HomeScreen} />
) : (
<>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen name="ConfirmEmail" component={ConfirmEmailScreen} />
<Stack.Screen name="ForgotPassword" component={ForgotPassword} />
<Stack.Screen name="NewPassword" component={NewPasswordScreen} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
};
export default Navigation;
Consume AuthContext from Navigation in Login so you are able to call setUser after login, like below (I added comments where I changed thing):
import { useNavigation } from "#react-navigation/native";
import React, { useContext, useState } from "react";
import {
Alert,
Image,
Keyboard,
KeyboardAvoidingView,
ScrollView,
StyleSheet,
TouchableWithoutFeedback,
useWindowDimensions,
View,
} from "react-native";
import Logo from "../../../assets/images/logo-main.png";
import CustomButton from "../../components/CustomButton/CustomButton";
import CustomInput from "../../components/CustomInput/CustomInput";
import { Auth } from "aws-amplify";
import { useForm } from "react-hook-form";
// Line I added
import {AuthContext} from "./Navigation" // ⚠️ use the correct path
const LoginScreen = () => {
// Line I added
const { setUser } = useContext(AuthContext);
const [loading, setLoading] = useState(false);
const { height } = useWindowDimensions();
const {
control,
handleSubmit,
formState: { errors },
} = useForm();
const navigation = useNavigation();
const onLoginPressed = async (data) => {
if (loading) {
return;
}
setLoading(true);
try {
const user = await Auth.signIn(data.username, data.password);
// Line I added
setUser(user);
} catch (e) {
Alert.alert("Opps", e.message);
}
setLoading(false);
};
const onForgotPasswordPressed = () => {
navigation.navigate("ForgotPassword");
};
const onRegisterPressed = () => {
navigation.navigate("Register");
};
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
>
<ScrollView
contentContainerStyle={{ flexGrow: 1, justifyContent: "center" }}
showsVerticalScrollIndicator={false}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.root}>
<Image
source={Logo}
style={[styles.logo, { height: height * 0.2 }]}
resizeMode={"contain"}
/>
<CustomInput
icon="user"
name="username"
placeholder="Username"
control={control}
rules={{ required: "Username is required" }}
/>
<CustomInput
icon="lock"
name="password"
placeholder="Password"
control={control}
rules={{ required: "Password is required" }}
secureTextEntry={true}
/>
<CustomButton
text={loading ? "Loading..." : "Login Account"}
onPress={handleSubmit(onLoginPressed)}
/>
<CustomButton
text="Forgot Password?"
onPress={onForgotPasswordPressed}
type="TERTIARY"
/>
<CustomButton
text="Don't have an account? Create one"
onPress={onRegisterPressed}
type="TERTIARY"
/>
</View>
</TouchableWithoutFeedback>
</ScrollView>
</KeyboardAvoidingView>
);
};
const styles = StyleSheet.create({
root: {
alignItems: "center",
padding: 20,
},
logo: {
width: 200,
maxWidth: 300,
maxHeight: 300,
},
});
I am using ActivityIndicator for showing the loading screen while my dispatch function dispatches the action and fetches the products from the firebase and renders it on my app screens But this is not happening. My app is showing products that are in store as dummy data and if I refresh the screen then it shows the data from firebase but not the loading spinner to show that loading is true.
ProductOverviewScreen.js:
import React, { useState, useEffect, useCallback } from "react";
import {
FlatList,
View,
Button,
Text,
StyleSheet,
Platform,
ActivityIndicator,
} from "react-native";
import { useSelector, useDispatch } from "react-redux";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import ProductItem from "../../components/shop/ProductItem";
import * as cartActions from "../../store/actions/cart";
import * as productActions from "../../store/actions/products";
import CustomHeaderButton from "../../components/UI/HeaderButton";
import Colors from "../../constants/Colors";
const ProductOverviewScreen = (props) => {
const [IsLoading, setIsLoading] = useState();
const [IsRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState();
const products = useSelector((state) => state.products.availableProducts);
const dispatch = useDispatch();
const loadedProducts = useCallback(() => {
setError(null);
setIsRefreshing(true);
dispatch(productActions.fetchProducts())
.then(setIsLoading(false))
.catch((err) => {
setError(err.message);
});
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
const willFocusSub = props.navigation.addListener(
"willFocus",
loadedProducts
);
return () => {
willFocusSub.remove();
};
}, [loadedProducts]);
useEffect(() => {
const loading = async () => {
setIsLoading(true);
await loadedProducts();
setIsLoading(false);
};
}, [dispatch, loadedProducts]);
const selectItemHandler = (id, title) => {
props.navigation.navigate("ProductDetail", {
productId: id,
productTitle: title,
});
};
const addToCartHandler = async (itemData) => {
setIsLoading(true);
await dispatch(cartActions.addToCart(itemData.item));
setIsLoading(false);
};
if (error) {
return (
<View style={styles.loadingSpiner}>
<Text>An Error occurred! </Text>
<Button
title="Try Again"
onPress={loadedProducts}
color={Colors.primary}
/>
</View>
);
}
if (IsLoading) {
return (
<View style={styles.loadingSpiner}>
<ActivityIndicator size="large" color={Colors.primary} />
</View>
);
}
if (!IsLoading && products.length === 0) {
return (
<View style={styles.loadingSpiner}>
<Text>No Product Found!</Text>
</View>
);
}
return (
<FlatList
data={products}
onRefresh={loadedProducts}
refreshing={IsRefreshing}
renderItem={(itemData) => (
<ProductItem
image={itemData.item.imageUrl}
title={itemData.item.title}
price={itemData.item.price}
onSelect={() => {
selectItemHandler(itemData.item.id, itemData.item.title);
}}
>
<Button
color={Colors.primary}
title="View Details"
onPress={() => {
selectItemHandler(itemData.item.id, itemData.item.title);
}}
/>
{IsLoading ? (
<ActivityIndicator size="small" color={Colors.primary} />
) : (
<Button
color={Colors.primary}
title="To Cart"
onPress={() => {
addToCartHandler(itemData);
}}
/>
)}
</ProductItem>
)}
/>
);
};
ProductOverviewScreen.navigationOptions = (navigationData) => {
return {
headerTitle: "All Products",
headerLeft: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="Menu"
iconName={Platform.OS === "android" ? "md-menu" : "ios-menu"}
color={Platform.OS === "android" ? Colors.primary : "white"}
onPress={() => {
navigationData.navigation.toggleDrawer();
}}
/>
</HeaderButtons>
),
headerRight: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="Cart"
iconName={Platform.OS === "android" ? "md-cart" : "ios-cart"}
onPress={() => {
navigationData.navigation.navigate("Cart");
}}
/>
</HeaderButtons>
),
};
};
const styles = StyleSheet.create({
loadingSpiner: {
flex: 1,
justifyContent: "center",
alignItems: "center",
opacity: 1,
},
});
export default ProductOverviewScreen;
I have also checked on both emulators IOS and android also on my real device. If I open the app on my real device then instantly app renders the data from the firebase but doesn't show a loading spinner.
In useEffect If I try to add dependency loading which costs async code and a function which fetches the data from firebase then it shows an error saying Can't find variable: loading.
Please making loadedProducts from sync to async
I am practicing moving from class components to functional components in react native and I am having trouble understanding how to access navigation from a component. Normally I would run something like this.props.navigation.navigate('screen'). Now I am normally passing navigation as a prop.
But it doesn't seem to work in the following example. Where am I going wrong? I am using react-navigation:
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Container, Form, Input, Item, Button, Label } from "native-base";
import * as firebase from "firebase";
const LoginForm = ({navigation}) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const addEmail = (email) => {
setEmail(email)
}
const addPassword = (password) => {
setPassword(password)
}
return (
<Form>
<Item floatingLabel>
<Label>Email</Label>
<Input
autoCorrect={false}
autoCapitalize="none"
onChangeText={(email)=>addEmail(email)}
/>
</Item>
<Item floatingLabel>
<Label>Password</Label>
<Input
secureTextEntry={true}
autoCorrect={false}
autoCapitalize="none"
onChangeText={(password)=>addPassword(password)}
/>
</Item>
<Button style={{ margin: 10 }}
full
rounded
success
onPress={()=>handleLogin(email, password, navigation)}
>
<Text style={{ color: 'white' }}>Login</Text>
</Button>
</Form>
);
};
export default LoginForm;
const handleLogin = (email, password, navigation) => {
console.log(email, password)
firebase.auth()
.signInWithEmailAndPassword(email.trim(), password)
.then(() => {firebase.auth().currentUser.emailVerified ? navigation.navigate('Home') : navigation.navigate('StartScreen')})
.catch(error => console.log(error))
}
I create a stack navigator in App.js
const bottomTabNavigator = createBottomTabNavigator(
{
Home: {
screen: Home,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Ionicons name="ios-home" size={25} color={tintColor}/>
// <Icon name="qrcode" size={25} color={tintColor} />
)
}
},
Profile: {
screen: Profile,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
// <Icon name="search" size={25} color={tintColor} />
<Ionicons name="md-person" size={25} color={tintColor}/>
)
}
},
},
{
initialRouteName: 'Home',
tabBarOptions: {
activeTintColor: '#eb6e3d'
}
}
);
const RootSwitch = createSwitchNavigator({
StartScreen,
Signup,
Login,
bottomTabNavigator
});
const AppContainer = createAppContainer(RootSwitch);
try using hooks, like this:
//add this import
import React, { useCallback } from 'react';
//change function to callback
const handleLogin = useCallback(async(email,password,navigation) => {
console.log(email, password)
await firebase.auth()
.signInWithEmailAndPassword(email.trim(), password)
.then(() => {firebase.auth().currentUser.emailVerified ? navigation.navigate('Home') : navigation.navigate('StartScreen')})
.catch(error => console.log(error))
}, []);
I have a form that a user can select a default local image or an image from the user's photo library
Here is an expo snack use android the images can be found in the phone menu in photos
I want to save either the default local image or user's image to the form and to redux, currently able to save default images picked to form and redux.
This is what currently works.
I have a component that gets a selected local image and returns an image path witch is a number. That local image gets saved in form and in redux. currently, the user can change the local image in the form.
ImgSelector Component:
import React, { useState } from "react";
import { List, Selector, View, SelectedImg } from "./styles";
import { FlatList } from "react-native";
import { defaultImages } from "../../data/defaultImages";
const FlatlistItem = ({ image, setImg }) => {
return (
<Selector onPress={() => setImg(image)}>
<View>
<SelectedImg source={image} />
</View>
</Selector>
);
};
const ImgSelector = ({ setImg }) => {
const [selectedId, setSelectedId] = useState(null);
const renderItem = ({ item }) => (
<FlatlistItem setImg={setImg} image={item.image} />
);
return (
<View>
<FlatList
horizontal
data={defaultImages}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
extraData={selectedId}
/>
</View>
);
};
export default ImgSelector;
Default local images are stored like this and the path is the index which is a number this part works fine.
export const defaultImages = [
{
id: “2”,
image: require("../assets/images/singlepane.png"),
}
]
I have an imagePicker component that asks for permissions and returns a uri string that looks like this:
file:/data/data/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FExpoWcPro-a828b17b-dcd7-4a04-93ca-657c8e4e511d/ImagePicker/6106d73f-c886-457d-abe9-1f1232a0d398.jpg
My form component where images are picked and saved:
import React, { useState } from "react";
import { Image } from "react-native";
const CounterForm = ({ navigation, ...props }) => {
// This is current state for default images that works
const [imgUrl, setImgUrl] = useState(props.imgUrl || defaultImage);
const [userImgUri, setUserImgUri] = useState(null);
// This gets the local image from a componnet
const handleEditImg = (newImgUrl) => {
setImgUrl(newImgUrl);
};
// This gets image uri from expo image picker
const handelUserImg = (userUri) => {
setUserImgUri(userUri);
};
// This sends data to a redux action to save
const handleSubmit = () => {
props.onFormSubmit({
id: props.id,
imgUrl,
});
setImgUrl(defaultImage);
};
return (
<FormWrapper>
<Row>
<FormButton onPress={() => handleSubmit()}>
<StyledText title="Save" color={COLORS.appBlue} />
</FormButton>
</Row>
<TopContent>
{/* I tried this to get user image and displays in form */}
<Image
source={{ uri: userImgUri }}
style={{ width: 100, height: 100 }}
/>
{/* This current implementation gets local images
<Image
source={imgUrl}
style={{ width: 100, height: 100 }}
/> */}
{/* I tried this only gets local images
{imgUrl ? (
<Image source={imgUrl} style={{ width: 100, height: 100 }} />
) : (
<Image
source={{ uri: userImgUri }}
style={{ width: 100, height: 100 }}
/>
)} */}
</TopContent>
<Row>
<ImagePicker getUserImg={handelUserImg} />
</Row>
<View>
<ImgSelector setImg={handleEditImg} />
</View>
</FormWrapper>
);
};
export default CounterForm;
if you use the last sdk version of Expo (40) and the right package expo-image-picker you need to follow this instructions.
First you need to ask for permissions :
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
}
And then call method to select image from library :
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
So you got image uri by accessing result.uri, you need to save this value (e.g in user store) and display it by selecting your store or default value if there is not stored value :
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Pick an image from camera roll" onPress={pickImage} />
/** imgUrl = stored image uri, defaultImages[0].image = default image uri */
<Image source={imgUrl ? { uri: imgUrl } : defaultImages[0].image} />
</View>
I found the answer it's updated in expo snack
import React, { useState } from "react";
import { List, Selector, View, SelectedImg } from "./styles";
import { FlatList } from "react-native";
import { defaultImages } from "../data";
const FlatlistItem = ({ image, setImg }) => {
return (
<Selector onPress={() => setImg(image)}>
<View>
<SelectedImg source={{uri: image}} />
</View>
</Selector>
);
};
const ImgSelector = ({ setImg }) => {
const [selectedId, setSelectedId] = useState(null);
const renderItem = ({ item }) => {
return (
<FlatlistItem setImg={setImg} image={item} />
)
}
return (
<View>
<FlatList
horizontal
data={defaultImages}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
extraData={selectedId}
/>
</View>
);
};
export default ImgSelector;
Form
import React, { useState } from "react";
import {Asset} from 'expo-asset';
import StyledText from "../UiComponents/StyledText";
import { TouchableWithoutFeedback, Keyboard } from "react-native";
import {
FormWrapper,
TextInputWrapper,
TopContent,
NumberWrapper,
Row,
FormButton,
View,
} from "./styles";
import StyledInput from "../UiComponents/StyledInput";
const defaultImage = Asset.fromModule(require('../assets/komloy.jpg')).uri
import WindowSelector from "../ImgSelector";
import StyledButton from "../UiComponents/StyledButton";
import ImagePicker from "../components/imagePicker";
import { Image } from "react-native";
const DismissKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
const CounterForm = ({ navigation, ...props }) => {
const [imgUrl, setImgUrl] = useState(props.imgUrl || defaultImage);
const handleEditImg = (newImgUrl) => {
setImgUrl(newImgUrl);
};
const handelUserImg = (userUri) => {
setImgUrl(userUri);
};
const handleSubmit = () => {
props.onFormSubmit({
id: props.id,
imgUrl
});
setImgUrl(defaultImage);
};
return (
<DismissKeyboard>
<FormWrapper>
<TopContent>
<Image
source={{uri: imgUrl}}
style={{ width: 100, height: 100 }}
/>
</TopContent>
<Row>
<StyledText title="Select a image" />
<ImagePicker getUserImg={handelUserImg} />
</Row>
<View>
<WindowSelector setImg={handleEditImg} />
</View>
</FormWrapper>
</DismissKeyboard>
);
};
export default CounterForm;
Data
import {Asset} from 'expo-asset';
const imageURI = Asset.fromModule(require('./assets/islands.jpg')).uri
const imageURI2 = Asset.fromModule(require('./assets/maeYai.jpg')).uri
export const defaultImages = [
imageURI, imageURI2
]
I understand that navigation and route props are automatically passed to the target screen when you navigate. So you can access navigation when you're in that screen, say screen B.
What I want to do is in Screen A, where the navigator is defined, I'd like to be able to setParams every so often. How can I refer to the navigator in this case?
For example, I would like to do this:
navigator.screen('posts').setParams({videoData: stuff});
A.js
const A = () => {
const Tab = createMaterialTopTabNavigator();
const navigator = <Tab.Navigator>
<Tab.Screen name="posts" component={FeedList} />
</Tab.Navigator>;
useEffect(()=> {
fetchStuff((stuff)=> {
navigator.screen('posts').setParams({videoData: dataState});
});
},[]);
return <>{navigator}</>;
}
Inside the useEffect, I ideally would be able to dynamically set the route params of the target screen.
I tried using initialParams and a state, but the initialParams doesn't seem to update in the target screen when the state changes. (So, I would accept that as an answer too, if you know why that's not working.)
not working initialParams example:
const A = () => {
const [dataState, setDataState] = useState(null);
const Tab = createMaterialTopTabNavigator();
useEffect(()=> {
fetchStuff((stuff)=> {
setDataState(stuff); // this doesn't seem to update initialParams
});
},[]);
return <Tab.Navigator>
<Tab.Screen name="posts" component={FeedList} initialParams={{videoData: dataState}} />
</Tab.Navigator>;
}
You can use setParams function.
Here is the code of updating of the state of a nested tab's screen after 3 seconds.
import React, {useContext, useState, createContext, useEffect} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {NavigationContainer} from '#react-navigation/native';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import {createStackNavigator} from '#react-navigation/stack';
import {CommonActions} from '#react-navigation/native';
const Stack = createStackNavigator();
const KeyContext = createContext();
const DELAY_TIME = 3000;
const KeyContextProvider = (props) => {
const [routeKey, setRouteKey] = useState(null);
return (
<KeyContext.Provider
value={{key: routeKey, setKey: (value) => setRouteKey(value)}}>
{props.children}
</KeyContext.Provider>
);
};
const AScreen = ({navigation}) => {
const Tab = createBottomTabNavigator();
const {key} = useContext(KeyContext);
React.useEffect(() => {
if (key) {
setTimeout(() => {
navigation.dispatch({
...CommonActions.setParams({value: 'Updated state'}),
source: key,
});
}, DELAY_TIME);
}
}, [navigation, key]);
return (
<Tab.Navigator>
<Tab.Screen
name="screen B"
component={BScreen}
initialParams={{value: 'Initial state'}}
/>
<Tab.Screen name="screen C" component={CScreen} />
</Tab.Navigator>
);
};
const BScreen = ({route}) => {
const val = route.params?.value || null;
const {setKey} = useContext(KeyContext);
useEffect(() => {
setKey(route.key);
}, [route, setKey]);
return (
<View style={styles.screen}>
<Text>Screen B</Text>
<Text>{val}</Text>
</View>
);
};
const CScreen = () => {
return (
<View style={styles.screen}>
<Text>Screen C</Text>
</View>
);
};
export default function App() {
return (
<KeyContextProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Screen A" component={AScreen} />
</Stack.Navigator>
</NavigationContainer>
</KeyContextProvider>
);
}
const styles = StyleSheet.create({
screen: {flex: 1, justifyContent: 'center', alignItems: 'center'},
});