I was having trouble with how to get and save my token in my login component to my app component that is done now, I can't log out in because I don't know how to get the token from the local storage and I don't know how to pass it to the other screen and catch it there. this is my app component :
const App = () => {
const [userToken, setUserToken] = useState(0);
useEffect(() => {
AsyncStorage.getItem('token').then((value) => {
if (value) {
setUserToken(value);
}
});
}, []);
return (
<NavigationContainer>
<Stack.Navigator>
{userToken == null ? (
<>
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Restablecer" component={RestablecerPasswd} />
<Stack.Screen name="Registrar" component={RegistrarNewUsr} />
</>
) : (
<>
<Stack.Screen name="Perfil" component={Perfil} />
<Stack.Screen name="Configuraciones" component={CambiarPsswd} />
<Stack.Screen name="Dietas" component={Dietas} />
<Stack.Screen name="Datos" component={Data} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
);
};
this is my profile component , this is where i want to catch the data that i get from a http request including the token that i use to enter this screen :
const Perfil = ({navigation}) => {
return (
<SafeAreaView>
<ScrollView>
<Text>Datos del Usuario</Text>
<Button title="Configuraciones" onPress={() => navigation.navigate('Configuraciones')}/>
<Button title="Mis Dietas" onPress={() => navigation.navigate('Dietas')}/>
<Button title="Datos Semanales" onPress={() => navigation.navigate('Datos')}/>
</ScrollView>
</SafeAreaView>
)
}
this is the way i get the token before enter the Perfil component screen:
const Login = ({navigation}) => {
const INITIAL_TOKEN = 0;
const token = useState(INITIAL_TOKEN);
const [email,setEmail] = useState('');
const [password,setPassword] = useState('');
const onEndGetDatos = (payload) => {
AsyncStorage.setItem('token', payload.data.token);
};
const sendDataL = () => {
Axios.post('http://3.90.64.114/api/v1/web/login',{
email,
password
}
).then(response => {
onEndGetDatos(response);
}).catch(err => {
alert(err);
})
}
useEffect(() => {
if (token !== INITIAL_TOKEN) {
AsyncStorage.setItem('token', `${token}`);
}
}, [token]);
return (
<SafeAreaView>
<ScrollView>
<TextInput placeholder="Usuario(email)"
onChangeText={email => setEmail(email)}
keyboardType = 'email-address'
/>
<TextInput placeholder="Contraseña"
secureTextEntry={true}
onChangeText={password => setPassword(password)}/>
<Button title="Entrar" onPress={sendDataL}/>
<Button title="Olvide mi Contraseña" onPress={() => navigation.navigate('Restablecer')}/>
<Button title="Soy Nuevo" onPress={() => navigation.navigate('Registrar')}/>
</ScrollView>
</SafeAreaView>
)
}
Your app is already getting the token from local storage when the app first renders here:
useEffect(() => {
AsyncStorage.getItem('token').then((value) => {
if (value) {
setUserToken(value);
}
});
}, []);
You can set a state value such as userLoggedIn to true and you can use that state value throughout your app.
When you want to logout, just clear the token value in local storage and set userLoggedIn state to false.
To pass this value to child components, you can use props or useContext() hook.
Related
when I tried to refactor the mobile app of 'Notes' from ‘JavaScript Everywhere’ book, a problem
The structure is like this:
RootNavigator(Stack.Navigator)
---AuthLoading(Screen)
---Auth(Stack.Navigator)
--SignIn(Screen)
--SignUp(Screen)
---App(Tab.Navigator)
--FeedStack(Stack.Navigator)
-FeedScreen(Screen)
-NoteScreen(Screen)
--MyNotesStack(Stack.Navigator)
-MyNotesScreen(Screen)
-NoteScreen(Screen)
--FavoritesStack(Stack.Navigator)
-FavoritesScreen(Screen)
-NoteScreen(Screen)
--SettingsStack(Stack.Navigator)
When a user login, the default tab is ‘Feed’ which goes to ‘FeedStack’, and FeedScreen list all the notes created by all the users, click one item of the notes, it goes to a NoteScreen, display the details of that ‘Note’, everything goes well.
When the user choose ‘MyNotes’ tab which goes to ‘MyNoteStack’, it list the notes created by the current user, click one of the ‘note’ item, it goes to NoteScreen, display the details of that ‘note’. However, now, the default focus of the Tab.Navigator is ‘Feed’, and when I click back button in the NoteScreen, it goes back to ‘FeedStack’ which displays all the notes. It is weird!
I can’t understand why I go to the NoteScreen from ‘MyNotes’, but back button leads it to ‘Feed’, how to fix this problem?
And the code is as follows.
In index.js (RootNavigator)
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
const AuthStack = createNativeStackNavigator();
const feedStack = createNativeStackNavigator();
const myNotesStack = createNativeStackNavigator();
const favoritesStack = createNativeStackNavigator();
const settingsStack = createNativeStackNavigator();
function FeedStack () {
return (
<feedStack.Navigator
screenOptions={
{headerShown:false}
}
>
<feedStack.Screen name="FeedScreen" component={FeedScreen} />
<feedStack.Screen name="NoteScreen" component={NoteScreen} options={{headerShown:true}}/>
</feedStack.Navigator>
);
}
function MyNotesStack () {
return (
<myNotesStack.Navigator
screenOptions={
{headerShown:false}
}
>
<myNotesStack.Screen name="MyNotesScreen" component={MyNotesScreen} />
<myNotesStack.Screen name="Note" component={NoteScreen} options={{headerShown:true}} />
</myNotesStack.Navigator>
);
}
function FavoritesStack () {
return (
<favoritesStack.Navigator
screenOptions={
{headerShown:false}
}
>
<favoritesStack.Screen name="FavoritesScreen" component={FavoritesScreen} />
<favoritesStack.Screen name="Note" component={NoteScreen} options={{headerShown:true}}/>
</favoritesStack.Navigator>
);
}
function SettingsStack () {
return (
<settingsStack.Navigator
screenOptions={
{headerShown:false}
}
>
<settingsStack.Screen name="SettingsScreen" component={SettingsScreen} />
</settingsStack.Navigator>
);
}
const TabNavigator = () => {
return (
<Tab.Navigator
initialRouteName="MyNotes"
activeColor='#f0f'
inactiveColor='#555'
barStyle={{
backgroundColor:'#999'
}}
screenOptions={({route}) => ({
headerShown: false,
tabBarIcon:({focused, size, color}) => {
let iconName;
if( route.name === 'FeedStack') {
iconName = 'home';
} else if (route.name === 'MyNotesStack') {
iconName = 'bed';
} else if (route.name === 'FavoritesStack') {
iconName = 'star'
} else {
iconName = 'spa'
}
color = focused ? '#f0f' : "#555";
size = focused ? 24 : 20;
return <FontAwesome5 name={iconName} size={size} color={color}/>;
},
})}
>
<Tab.Screen name='FeedStack' component={FeedStack} options={{headerShown: false}} />
<Tab.Screen name='MyNotesStack' component={MyNotesStack} options={{headerShown: false}} />
<Tab.Screen name='FavoritesStack' component={FavoritesStack} options={{headerShown: false}}/>
<Tab.Screen name='SettingsStack' component={SettingsStack} options={{headerShown: false}} />
</Tab.Navigator>
);
};
const Auth= () => {
return (
<AuthStack.Navigator
screenOptions={{headerShown:false}}
>
<AuthStack.Screen name='signIn' component={SignIn}></AuthStack.Screen>
</AuthStack.Navigator>
);
};
const RootNavigator = () => {
return (
<Stack.Navigator initialRouteName='AuthLoading'>
<Stack.Screen name='AuthLoading'
component={AuthLoading}
options={{title:'AuthLoading'}}
>
</Stack.Screen>
<Stack.Screen name='Auth'
component={Auth}
options={{
title: 'Auth',
headerStyle: {
backgroundColor: '#f4511e',
},
headerBackVisible: false,
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
</Stack.Screen>
<Stack.Screen name='App'
component={TabNavigator}
options={{
title: 'App',
headerStyle: { backgroundColor: '#f4511e'},
headerBackVisible:false,
headerTintColor: '#fff',
headerTitleStyle: {fontWeight: 'bold'},
}}
>
</Stack.Screen>
</Stack.Navigator>
);
};
export default RootNavigator;
In FeedScreen.js:
const FeedScreen = () => {
const { data, loading, error } = useQuery(GET_NOTES);
// if the data is loading, our app will display a loading indicator
if(loading)
return <Loading />;
if(error)
return <Text>Error loading notes.</Text>;
// if the query is successful and there are notes, return the feed of notes
return (
<NoteFeed notes={data.notes} />
);
};
In MyNotesScreen.js
const MyNotesScreen = () => {
const { data, loading, error } = useQuery(GET_MY_NOTES);
// if the data is loading, our app will display a loading indicator
if(loading)
return <Loading />;
if(error)
return <Text>Error loading MyNotes.</Text>;
// if the query is successful and there are notes, return the feed of notes
// else if the query is successful and there aren't notes, display a message
if(data.me.notes.length !== 0) {
return <NoteFeed notes={data.me.notes} />;
} else {
return <Text>No notes yet</Text>;
}
// If I don't use <NoteFeed> here, for example, show a button then go to <NoteScreen> it is ok.
// return (
// <View style={styles.container}>
// <Text>My Notes Screen</Text>
// use self-defined button
// <JereButton
// onPress={() => navigation.navigate('Note',{id:'63b94da5ccf7f90023169c3d'})}
// title="Go to a note"
// color={"#882"}
// />
// </View>
// );
};
In NoteFeed.js
const NoteFeed = props => {
// only screen components receive navigation prop automatically!
// if you wish to access the navigation prop in any of your components, you may use the useNavigation hook.
const navigation = useNavigation();
return (
<View style={styles.container}>
<FlatList
data={props.notes}
keyExtractor = {({id}) => id.toString()}
renderItem = {({item}) => (
<Pressable style={styles.feedview}
onPress={() => navigation.navigate('NoteScreen',{id: item.id})}
>
<Text style={styles.text}>{item.content}</Text>
</Pressable>
)}
/>
</View>
);
};
In NoteScreen.js
const NoteScreen = ({navigation, route}) => {
const {id} = route.params;
const { data, loading, error } = useQuery(GET_NOTE, {variables:{id}});
// if the data is loading, our app will display a loading indicator
if(loading)
return <Loading />;
if(error)
return <Text>Error Note not found.</Text>;
return (
<Note note={data.note} />
);
};
Thank you for your help.
I tried to replace useNavigation() to props solution, the issue is the same. Then I tried to do not use in 'MyNotes' to show the ‘note’, it is OK, but it doesn’t comply with the design.
I have a simple expo managed React Native project setup with react-navigation
I have 2 screens one of which is the Home screen and the other is a screen for QRCode scanner which uses expo's BarCodeScanner.
The issue here is that when navigating from the Home screen to the QRCode screen and back to the Home screen keeps the camera alive. I can see the activity in the status bar saying 'Expo Go is using your camera'
I tried various ways to tackle this,
Passing the screen as a render prop to Stack.Screen so it mounts every time we navigate, but still the same issue
Tried using the isFocused method to conditionally render the component but still no luck
<NavigationContainer fallback={<Text>Loading...</Text>}>
<Stack.Navigator
screenOptions={({ route, navigation }) => ({
headerShadowVisible: false,
headerTitle: () => (
<Text
style={{
fontSize: 30,
fontFamily: Font["900"],
}}
>
Test
</Text>
),
headerRight: () =>
route.name !== "QR" ? (
<Pressable onPress={() => navigation.navigate("QR")}>
<QrcodeIcon size={26} color="black" />
</Pressable>
) : null,
})}
>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="QR" children={() => <QRCode />} />
</Stack.Navigator>
</NavigationContainer>
And the code for the QRCode component looks like the following:
const QRCode = () => {
const [hasPermission, setHasPermission] = useState<boolean>();
const [scanned, setScanned] = useState<boolean>(false);
const isFocused = useIsFocused();
const linkTo = useLinkTo();
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
}, []);
const handleBarCodeScanned = ({ type, data }: BarCodeEvent) => {
setScanned(true);
linkTo(data);
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View style={styles.container}>
{isFocused ? (
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFill}
/>
) : null}
</View>
);
};
App.tsx
const Tab = createMaterialTopTabNavigator();
export default function App() {
const [text, setText] = useState('');
const [search, setSearch] = useState('');
const searchHandler = () => {
setSearch(text)
}
return (
<NavigationContainer>
<View style={styles.case1}></View>
<View style={styles.case1}>
<TextInput
style={styles.input}
placeholder='search'
onChangeText={(val) => setText(val)}
/>
<Button
onPress={searchHandler}
title="search button"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
</View>
<Tab.Navigator>
<Tab.Screen name="google1" component={() => <Google item={search} />} />
<Tab.Screen name="google2" component={() => <Google2 item={search} />} />
</Tab.Navigator>
</NavigationContainer>
);
}
Google.tsx
export default function google(props) {
return (
<View style={styles.webContainer}>
<WebView
source={{
uri:
'https://www.google.com/search?hl=en&sxsrf=ALeKk03BNJhsVEURZyXdlkOpk1l1qb2Nug%3A1595674787434&source=hp&ei=oxAcX4qzGLLxhwOhw5jwBA&q=' + props.item + '&oq=' +** props.item ** +'&gs_lcp=CgZwc3ktYWIQAzIFCC4QkwIyAggAMgIIADICCC4yAggAMgIIADICCAAyAggAMgIIADICCAA6BAgjECc6BQguEJECOgUIABCRAjoICC4QxwEQowI6BwguEEMQkwI6CAguEMcBEK8BUPEDWOkHYKwIaABwAHgAgAGMAYgBigOSAQMwLjOYAQCgAQGqAQdnd3Mtd2l6&sclient=psy-ab&ved=0ahUKEwjKkIXnn-jqAhWy-GEKHaEhBk4Q4dUDCAc&uact=5'
}}
/>
</View>
);
};
I'm creating an app that searches across different sites by typing in the search bar.
Whenever I type in the web-view keeps re-rendering. I only want to be re-rendered(Google web-view) when I click the search button.
I've heard that should use use-memo, Callback, as far as I know.
Thanks
const searchHandler = () => {
setSearch(text) <- "text" is a reference
}
try
const searchHandler = () => {
setSearch(`${text}`)
}
Prevent rerender WebView from props through useMemo. (memoization)
export default function google(props) {
const Web = React.useMemo(() => {
return <WebView source={{uri: 'abc'}} />
}, [])
return (
<View style={styles.webContainer}>
{Web}
</View>
);
};
I'm quiete new to react native, and im stuck on passing context between components in different files
basically im building login flow following the react navigation auth-flow https://reactnavigation.org/docs/auth-flow/
my scenario looks as follow:
in App.js
a stack screen with Login/Register/Home, showing Login/Register or Home based on login status
the Home Screen is made by a component which is a drawer, using a custom drawer and two component (Home and About)
//VARIOUS IMPORT
const Drawer = createDrawerNavigator();
const HeaderOption = () => ({
headerShown: false,
// animationTypeForReplace: state.isSignout ? 'pop' : 'push',
});
const AppStack = createStackNavigator();
const AuthContext = createContext();
//THE DRAWER FOR HOME
function DrawerNavigator(props) {
return (
<Drawer.Navigator
initialRouteName="Home"
drawerContent={(props) => MyDrawer(props)}
>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="About" component={About} />
</Drawer.Navigator>
);
}
//MAIN APP
export default function App({ navigation }) {
const [state, dispatch] = useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
userToken: null,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
}
);
useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem('userToken');
} catch (e) {
}
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};
bootstrapAsync();
}, []);
const authContext = useMemo(
() => ({
signIn: async (data) => {
// LOGIN PROCEDURE
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async (data) => {
// SUBSCRIBE PROCEDURE
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
if (state.isLoading) {
// We haven't finished checking for the token yet
return (
<View>
<Text>Loading</Text>
</View>
);
}
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<AppStack.Navigator initialRouteName="Login">
{state.userToken == null ? (
<>
<AppStack.Screen
name="Login"
component={LoginScreen}
options={HeaderOption}
/>
<AppStack.Screen
name="Register"
component={RegisterScreen}
options={HeaderOption}
/>
</>
) : (
<AppStack.Screen
name="HomeApp"
component={DrawerNavigator}
options={HeaderOption}
/>
)}
</AppStack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
}
in LoginScreen.js
the effective login screen (which is showed at app startup if not logged in)
//import
export default function LoginScreen(props) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { signIn } = useContext(AuthContext);
return (
<View
style={{
flex: 1,
backgroundColor: Constants.MAIN_GREEN,
}}
>
<View style={{ ...styles.container }}>
<StatusBar hidden={true} />
<View style={{ ...styles.logoContainer }}>
<Image
style={styles.logoIcon}
source={require('../assets/logo_popeating_amp.png')}
/>
</View>
<View style={{ ...styles.inputContainer }}>
<Image
style={styles.inputIcon}
source={require('../assets/mail.png')}
/>
<TextInput
autoFocus={true}
placeholder="Email address"
onChangeText={(email) => setEmail(email)}
value={email}
label="Email"
style={styles.inputs}
keyboardType={'email-address'}
/>
</View>
<View style={{ ...styles.inputContainer }}>
<Image
style={styles.inputIcon}
source={require('../assets/password.png')}
/>
<TextInput
placeholder="Password"
onChangeText={(password) => setPassword(password)}
value={password}
secureTextEntry={true}
label="Password"
style={styles.inputs}
/>
</View>
<TouchableHighlight
style={[styles.buttonContainer, styles.loginButton]}
onPress={() => signIn({ email, password })}
underlayColor={Constants.HI_COLOR}
>
<Text style={styles.loginText}>LOGIN</Text>
</TouchableHighlight>
<TouchableHighlight
style={styles.buttonContainer}
onPress={() => props.navigation.navigate('HomeApp')}
underlayColor={Constants.HI_COLOR}
>
<Text>Forgot your password?</Text>
</TouchableHighlight>
<TouchableHighlight
style={styles.buttonContainer}
onPress={() => props.navigation.navigate('Register')}
underlayColor={Constants.HI_COLOR}
>
<Text>Register</Text>
</TouchableHighlight>
</View>
</View>
);
}
const styles = StyleSheet.create({
//styles
});
in DrawerContent.js
the drawer for the home which contain a link to Home, a link to About, a link to Logout
in Home.js
the main page which is the initialroute of the Drawer
every time i try to start the app
the error is
Unhandled promise rejection: ReferenceError: Can't find variable: AuthContext
it seems LoginScreen cant access AuthContext, how can i have AuthContext available to other components between files?
You can place the context creation in a separate file
//AuthContext.js
const AuthContext = createContext();
export default AuthContext;
In app.js you can simply import this and use this
import AuthContext from './AuthContext.js';
You can do the same for login.js as well
Then it will work as expected.
I am using React-Navigation v5 with Redux. I like to call my logOut action creator to trigger my log out function from my headerRight.
However, I can only access logOut from inside Home element and not inside HomeStack.
One idea I have is to also wrap my HomeStack with connect. I haven't tried it yet to know whether it can work or not. Even should it work, this isn't my preferred solution because i feel it makes my code very verbose
Anyone has a solution on how to access my logOut function from within my HomeStack? Thanks in advance
const Home = props => {
const { loading, error, data, subscribeToMore } = useQuery(FIND_BOOKS_QUERY);
console.log("home props", props);
return (
<View>
<Text> Welcome {props.user && props.user.name} </Text>
{loading && <Text>Loading...</Text>}
{error && <Text>Error: {error.message}</Text>}
{data &&
data.findBooks.map((x, index) => {
return (
<View key={index}>
<Text>
{x.title} - {x.author}
</Text>
</View>
);
})}
</View>
);
};
const HomeContainer = connect(
state => {
// console.log("home state", state);
return {
user: state.auth.user
};
},
{ logOut }
)(Home);
export const HomeStack = props => {
console.log("home stack props", props);
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeContainer}
options={{
headerRight: () => {
return (
<TouchableOpacity
onPress={() => {
// props.logOut // - cannot access logOut here as HomeStack props does not have logOut
console.log("exit");
}}
>
<Text>Exit</Text>
</TouchableOpacity>
);
}
}}
/>
</Stack.Navigator>
);
};
Wrapping my HomeStack with connect works. Its able to make available logOut inside H which allows me to call logOut inside headerRight.
However, such a method will be verbose because I will need to connect both H and Home to redux. Is there a more elegant way? Thanks
const Home = props => {
const { loading, error, data, subscribeToMore } = useQuery(FIND_BOOKS_QUERY);
console.log("home props", props);
return (
<View>
<Text> Welcome {props.user && props.user.name} </Text>
{loading && <Text>Loading...</Text>}
{error && <Text>Error: {error.message}</Text>}
{data &&
data.findBooks.map((x, index) => {
return (
<View key={index}>
<Text>
{x.title} - {x.author}
</Text>
</View>
);
})}
</View>
);
};
const HomeContainer = connect(
state => {
// console.log("home state", state);
return {
user: state.auth.user
};
},
{ logOut }
)(Home);
export const H = props => {
console.log("home stack props", props);
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeContainer}
options={{
headerRight: () => {
return (
<TouchableOpacity
onPress={() => {
props.logOut(); // now works OK
console.log("exit");
}}
>
<Text>Exit</Text>
</TouchableOpacity>
);
}
}}
/>
</Stack.Navigator>
);
};
export const HomeStack = connect(
state => {
// console.log("home state", state);
return {
user: state.auth.user
};
},
{ logOut }
)(H);