How i can re-render Drawer in React Native? - reactjs

I'm implementing for the first time the login with Mysql and php in react native, and everything works correctly but the only problem i have is that the content of the Drawer is not updated until i restart the app. I'm implementing AsyncStorage to save if the user is logged or not, if he is logged then return the My Profile button and if it does not return the Sign In button. I have tried this: props.navigation.state.params.refresh(); instead props.navigation.navigate('home'); but does not work for me. Any suggestion? I'm doing something wrong?
Login Function
const login = async() => {
if (email, password) {
signInApi(email, password).then(response => {
if (response != 'error') {
setLogged(true);
props.navigation.navigate('home');
}else if(response === 'error'){
setLogged(false);
}
});
}else{
Alert.alert('Complete Form');
}
}
DrawerContent
export default function DrawerContent(props){
const {navigation} = props;
const [isLogged, setisLogged] = useState(null);
const onChangeScreen = (screen) => {
navigation.navigate(screen);
};
const checkLogged = async () => {
const response = await getLogged();
setisLogged(response);
}
useEffect(() => {
checkLogged();
}
}, []);
renderItem = () => {
if (!isLogged || isLogged === 'false') {
return (
<Button onPress={() => onChangeScreen("signin")}>
Sign In
</Button>
);
}else{
return (
<Button onPress={() => onChangeScreen("profile")}>
My Profile
</Button>
);
}
};
return (
{this.renderItem()}
);
Navigation Drawer
import DrawerContent from './DrawerContent';
const Drawer = createDrawerNavigator();
const DrawerNav = () => {
return (
<Drawer.Navigator initialRouteName="app" drawerContent={(props) => <DrawerContent {...props} />}>
<Drawer.Screen name="app" component={StackNavigation} />
</Drawer.Navigator>
);
};
export default DrawerNav;

Related

Get AdSense Ads to Reload on Route Change in Next JS

We've implemented Google AdSense on our Next.JS website, but we'd like to have ads reload whenever the route changes.
Here is what I tried:
const AdBlock = props => {
const { userLoaded, currentUserIsSilverPlus } = useAuth()
useEffect(() => {
window.adsbygoogle = window.adsbygoogle || []
window.adsbygoogle.push({})
}, [])
const reloadAds = () => {
const { googletag } = window
if (googletag) {
googletag.cmd.push(function () {
googletag.pubads().refresh()
})
}
}
Router.events.on("routeChangeStart", reloadAds)
if (userLoaded && !currentUserIsSilverPlus) {
return (
<ins
className="adsbygoogle adbanner-customize"
style={{ display: "block" }}
data-ad-client="ca-pub-1702181491157560"
data-ad-slot={props.slot}
data-ad-format={props.format}
data-full-width-responsive={props.responsive}
/>
)
}
return ""
}
export default AdBlock
The ads load, however, they never appear to refresh. What am I missing?
My solution was to observe router event changes, when routeChangeStart ads had to be unmounted, when routeChangeComplete they had to be mounted remounted.
// components/Adsense.tsx
function Ads() {
const adsRef = useRef<HTMLModElement | null>(null);
useEffect(() => {
const executeWindowAds = () => {
try {
// #ts-ignore
(adsbygoogle = window.adsbygoogle || []).push({});
} catch (error: any) {
console.error('error ads======>', error.message);
}
};
const insHasChildren = adsRef.current?.childNodes.length;
if (!insHasChildren) {
executeWindowAds();
}
}, []);
return (
<ins
ref={adsRef}
className="adsbygoogle"
style={{ display: 'block' }}
data-ad-client="ca-pub-xxx"
data-ad-slot="xxx"
data-ad-format="auto"
data-full-width-responsive="true"
></ins>
);
}
export default function Adsense() {
const router = useRouter();
const [shouldMount, setShouldMount] = useState(true);
useEffect(() => {
const onRouteChangeStart = () => setShouldMount(false);
const onRouteChangeComplete = () => setShouldMount(true);
router.events.on('routeChangeStart', onRouteChangeStart);
router.events.on('routeChangeComplete', onRouteChangeComplete);
return () => {
router.events.off('routeChangeStart', onRouteChangeStart);
router.events.off('routeChangeComplete', onRouteChangeComplete);
};
}, [router.events]);
return shouldMount ? <Ads /> : null;
}
On your layout page, for example a sidebar where each page has the same sidebar:
// components/LayoutBlogPost.tsx
import dynamic from 'next/dynamic';
const Adsense = dynamic(() => import('./Adsense'), { ssr: false });
export default function LayoutBlogPost({children}: {children: any}) {
return (
<div>
{children}
<Adsense />
</div>
);
}
try useRouter hook and handling router events inside useEffect:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter();
useEffect(() => {
router.events.on('routeChangeError', handleChange)
}, [])
return (
<>
</>
)
}

React Native Firebase refresh user record

I'm currently building a firebase login system with a verified email screen.
My problem is that I have a reload user button on the verified email screen that updates the user's credentials so that my root directory redirects the user to the AppStack if currentUser.emailVerified === true.
but the reload button isn't being triggered once pressed so that my root directory is still currentUser.emailVerified === false and not redirecting the user to the AppStack.
Login-System/context/AuthContext.js:
import React, { createContext, useContext, useState, useEffect } from 'react';
import { auth } from '../config';
const AuthContext = createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
function sendVerification() {
return currentUser.sendEmailVerification();
}
const getUser = () => auth.currentUser;
const reloadUser = () => getUser().reload();
const reload = async () => {
try {
await reloadUser();
const user = getUser();
setCurrentUser(user);
} catch (error) {}
return reload;
};
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user);
setLoading(false);
});
return () => {
unsubscribe();
};
}, []);
const value = {
currentUser,
loading,
reload,
sendVerification,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
Login-System/screens/VerifyEmailScreen.js:
import React from 'react';
import { Text, StyleSheet } from 'react-native';
import { useAuth } from '../contexts/AuthContext';
import { View, Button } from '../components';
import { Colors } from '../config';
export const VerifyEmailScreen = () => {
const { currentUser, reload, sendVerification } = useAuth();
const handleSendVerification = async () => {
try {
await sendVerification();
} catch (error) {}
return handleSendVerification;
};
return (
<>
<View isSafe style={styles.container}>
<View style={styles.center}>
<Text style={styles.screenTitle}>Check your email</Text>
<Text style={styles.screenInfo}>{currentUser.email}</Text>
<Text style={styles.screenInfo}>
We sent you an email with instructions on how to verify your email
address. Click on the link in the email to get started.
</Text>
<Button
style={styles.button}
onPress={() => handleSendVerification()}>
<Text style={styles.buttonText}>Resend</Text>
</Button>
<Button style={styles.button} onPress={reload}>
<Text style={styles.buttonText}>Done</Text>
</Button>
</View>
</View>
</>
);
};
I did some similar works to check if the user email is verified or not you can use this function:
export function getUserEmailVerified() {
const user = firebase.auth().currentUser;
return !!user ? (user.emailVerified ? 'Yes' : 'No') : 'No';
}
To trigger an email verification you can use this method. This method will trigger an email verification and refresh the user.
export function verificationEmail(email, onSuccess) {
refreshUser();
const user = firebase.auth().currentUser;
const finishAction = message => {
onSuccess();
showToast(message);
};
user
.sendEmailVerification()
.then(() => {
refreshUser();
})
.catch(error => finishAction(error.message));
}
And to refresh the user you can use this method.
export function refreshUser() {
let user = firebase.auth().currentUser;
if (!!user && !user?.emailVerified) {
interval = setInterval(() => {
user?.reload().then();
}, 3000);
}
}
You also need to use onUserChanged from firebase to detect whether user information is changed or not. It returns a listener if your user email verified field is changed anyway it will get here is an example.
export function onUserChanged(listener) {
firebase.auth().onUserChanged(listener);
}

React Native Webview onShouldStartLoadWithRequest does not detect history.push()?

I'm trying to make my React web application native using React Native Webview. In order to implement native social oauth, I made a screen that requires users sign in and after the users sign in, a webView with my web application url is rendered:
const App = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
// Check if the app has valid tokens
}, []);
return (
<AuthContext.Provider
value={{
isLoggedIn,
login: () => setIsLoggedIn(true),
logout: () => setIsLoggedIn(false),
}}>
{isLoggedIn ? (
<SafeAreaView style={styles.container}>
<MyWebView />
</SafeAreaView>
) : (
<LoginScreen />
)}
</AuthContext.Provider>
);
};
If users navigate to a page in my domain, my app renders it in MyWebView, otherwise my app opens the os browser with the external url. Also, I want my app to confirm and set isLoggedIn to false so that the app stops rendering MyWebView and goes back to LoginScreen, if users try to navigate to ${BASE_URL}/logout, :
const MyWebView = () => {
const { logout: appLogout } = useContext(AuthContext);
const askForLogout = () => {
const title = 'Logout';
const message = 'Do you want to sign out?';
return new Promise((resolve, reject) => {
Alert.alert(
title,
message,
[
{ text: 'cancel', onPress: () => resolve(false) },
{ text: 'OK', onPress: () => resolve(true) },
],
{ cancelable: false },
);
reject('Error');
});
};
const onLogout = async () => {
try {
const shouldLogout = await askForLogout();
if (!shouldLogout) {
return;
}
await logout(); // This expires tokens
appLogout();
} catch (e) {
alertError();
}
};
return (
<WebView
source={{ uri: BASE_URL }}
sharedCookiesEnabled={true}
startInLoadingState={true}
renderLoading={() => <ActivityIndicator />}
onShouldStartLoadWithRequest={navState => {
const { url } = navState;
if (url.startsWith(BASE_URL)) {
if (url.includes('/logout')) {
onLogout();
return false;
}
return true;
} else {
// External Links
Linking.openURL(url);
return false;
}
}}
/>
);
};
export default MyWebView;
The problem is, onShouldStartLoadWithRequest sometimes (almost always) does not detect navigations using history.push(path) so my app cannot detect whether users navigate to /logout and call the logout function. onNavigationStateChange can detect history.push(path), but with onNavigationStateChange my app cannot stops external links from being rendered in MyWebView (This is bad because some external links use insecure HTTP, causing NSURLErrorDomain)
Any help would be really appreciated. Thank you!

How to prevent UI flickering while conditionally rendering components?

Consider the following code:
const Home = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(authUser => {
if(authUser) {
setUser(authUser);
} else {
setUser(null)
}
});
return () => unsubscribe();
}, []);
return (
<div>
{user ? (
<Hero />
) : (
<Login />
)}
</div>
)
}
export default Home
The Login component has all the functions which handles all the Sign Up, Login and Third-Party Authentications using Firebase.
The problems are:
When I reload the page and if the user is already logged in, it shows the component for some time, and then renders the component, which gives a bad UX.
Also, when I sign in using Google or Facebook, again this component is rendered before finally rendering the component.
Please throw some light into this issue. Your help will be highly appreciated!
Edit:
Problem 1 is solved, but problem 2 is not. Here is the relevant code for problem 2:
Login.js
<div style={{ marginBottom: "2%" }}>
<GoogleSignup />
</div>
GoogleSignup.js
import { GoogleLoginButton } from "react-social-login-buttons";
import firebase from "firebase";
import fire from "../fire";
const GoogleSignup = ({ extensionId }) => {
const OnSubmitButton = async () => {
var provider = new firebase.auth.GoogleAuthProvider();
fire
.auth()
.signInWithPopup(provider)
.then((result) => {
const credential = result.credential;
const token = credential.accessToken;
const user = result.user;
})
.catch((error) => {
console.log(error);
});
};
return (
<div>
<GoogleLoginButton
style={{ fontSize: "17px" }}
text={"Continue with Google"}
align={"center"}
onClick={OnSubmitButton}
/>
</div>
);
};
export default GoogleSignup;
These lines:
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(authUser => {
if(authUser) {
setUser(authUser);
} else {
setUser(null)
}
});
return () => unsubscribe();
}, []);
can be replaced with just:
useEffect(() => auth.onAuthStateChanged(setUser), []);
Next, instead of passing in just null to the useState, pass in current user.
const [user, setUser] = useState(null);
becomes
const [user, setUser] = useState(auth.currentUser);
This results in:
const Home = () => {
const [user, setUser] = useState(auth.currentUser);
useEffect(() => auth.onAuthStateChanged(setUser), []);
return (
<div>
{user ? (
<Hero />
) : (
<Login />
)}
</div>
)
}
export default Home
Personally, I tend to use undefined/null/firebase.auth.User using:
const Home = () => {
const [user, setUser] = useState(() => firebase.auth().currentUser || undefined);
const loadingUser = user === undefined;
useEffect(() => firebase.auth().onAuthStateChanged(setUser), []);
if (loadingUser)
return null; // or show loading icon, etc.
return (
<div>
{user ? (
<Hero />
) : (
<Login />
)}
</div>
)
}
export default Home
After the popup has closed, Firebase Authentication still needs to handle the authentication flow of exchanging the provider's authentication token for a Firebase User token. While this is taking place, you should show some form of loading screen in your component. In the below code sample, I change the "Continue with Google" text to "Signing in..." and disable the onClick events for each button while the sign in process takes place.
import { GoogleLoginButton } from "react-social-login-buttons";
import firebase from "firebase";
import fire from "../fire";
const PROVIDER_ID_GOOGLE = firebase.auth.GoogleAuthProvider.PROVIDER_ID;
const ignoreOnClick = () => {};
const GoogleSignup = ({ extensionId }) => {
const [activeSignInMethod, setActiveSignInMethod] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
if (activeSignInMethod === null)
return; // do nothing.
let disposed = false, provider;
switch (activeSignInMethod) {
case PROVIDER_ID_GOOGLE:
provider = new firebase.auth.GoogleAuthProvider();
break;
default:
// this is here to help catch when you've added a button
// but forgot to add the provider as a case above
setError("Unsupported authentication provider");
return;
}
fire.auth()
.signInWithPopup(provider)
.then((result) => {
// const credential = result.credential;
// const token = credential.accessToken;
// const user = result.user;
if (!disposed) {
setError(null);
setActiveSignInMethod(null);
}
})
.catch((error) => {
console.error(`Failed to sign in using ${activeSignInMethod}`, error);
if (!disposed) {
setError("Failed to sign in!");
setActiveSignInMethod(null);
}
});
return () => disposed = true; // <- this is to prevent any "updating destroyed component" errors
}, [activeSignInMethod]);
return (
{ error && (<div key="error">{error}</div>) }
<div key="signin-list">
<GoogleLoginButton
style={{ fontSize: "17px" }}
text={
activeSignInMethod == PROVIDER_ID_GOOGLE
? "Signing in..."
: "Continue with Google"
}
align={"center"}
onClick={
activeSignInMethod === null
? () => setActiveSignInMethod(PROVIDER_ID_GOOGLE)
: ignoreOnClick
}
/>
</div>
);
};
export default GoogleSignup;

KeyCloak React refreshToken expired token

I want to implement authorization in my client-side application but I've got problem with update Token in React Application with Keycloak.
App.js
import keycloak from "../../keycloak";
const App = () => {
const handleOnEvent = async (event,error) => {
if(event === 'onTokenExpired'){
keycloak.updateToken(300).then(
(response) => {
//I want to update my existing Token
alert("response: ", response )
})
.catch(error => {
console.log("error: ", error)
})
}
}
return (
<>
<ReactKeycloakProvider
authClient={keycloak}
onEvent={(event,error) => handleOnEvent(event,error)}>
<AppRouter/>
</ReactKeycloakProvider>
</>)
}
export default App;
Header
const Header = () => {
const {keycloak,initialized} = useKeycloak()
useEffect(() => {
if(keycloak.authenticated){
alert(JSON.stringify(keycloak))
localStorage.setItem("keycloakToken", keycloak.token); //set keycloak token to localStorag
localStorage.setItem("keycloakRefreshToken", keycloak.refreshToken); // set refresh token
setJWTToken(keycloak.token) //set to axios Authorization Bearer
}
},[keycloak.authenticated])
return(
<>
{
keycloak && !keycloak.authenticated && <UnloggedHeader keycloak={keycloak}/>
}
{
keycloak && keycloak.authenticated && <LoggedHeader keycloak={keycloak}/>
}
</>
)
}
export default Header
UnloggedHeader
function UnloggedHeader({keycloak}){
const signIn = () => {
keycloak.login()
}
return (
<div style={{minWidth: '1100px'}}>
<AppBar position="sticky" color='transparent'>
<Toolbar>
<Button onClick={signIn} variant="contained" color="primary">Login</Button>
<Typography variant="body1" component="h6">Unlogged</Typography>
</Toolbar>
</AppBar>
</div>
);
}
export default UnloggedHeader
LoggedHeader
function LoggedHeader({keycloak}){
let history = useHistory()
const [anchorEl, setAnchorEl] = React.useState(null);
const isMenuOpen = Boolean(anchorEl);
const handleProfileMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const [userInfo,setUserInfo] = useState()
useEffect(() => {
keycloak.loadUserInfo().then(userInfo => {
setUserInfo(userInfo)
localStorage.setItem("username", userInfo.preferred_username); // set username of user
})
},[])
const handleMenuClose = () => {
setAnchorEl(null);
};
const handleUserLogoutClick = () => {
keycloak.logout()
history.push("/")
}
return (
<div style={{minWidth: '1100px'}}>
<AppBar position="sticky" color='transparent'>
<Toolbar>
<Typography variant="body1" component="h6">{userInfo !== undefined ? userInfo.preferred_username : "EMPTY"}</Typography>
<ExpandMoreIcon/>
<Button onClick={handleUserLogoutClick} variant="contained" color="primary">Log out</Button>
</Toolbar>
</AppBar>
{renderMenu}
</div>
);
}
export default LoggedHeader
keycloak.js
import Keycloak from 'keycloak-js'
const keycloakConfig = {
url: 'http://10.192.168.72:8080/auth/',
realm: 'Realm12',
clientId: 'client',
}
const keycloak = new Keycloak(keycloakConfig);
export default keycloak
What I need provide to ReactKeycloakProvider to get new access_token when was expired ?
How based on refreshToken value get accessToken? I don't know which method or endpoint due to get this value. I can't find this kind of problem in network.
Please help me !
You can use event onTokens on Provider
<ReactKeycloakProvider
authClient={keycloak}
onTokens={({ token }) => {
// dispatch(setToken(token));
localStorage.setItem("keycloakToken", token);
}}
<AppRouter/>
</ReactKeycloakProvider>
And to trigger the update method, you can listen the event in your app router like this
export default function AppRouter() {
const { initialized, keycloak } = useKeycloak<KeycloakInstance>();
useEffect(() => {
if (keycloak && initialized) {
keycloak.onTokenExpired = () => keycloak.updateToken(600);
}
return () => {
if (keycloak) keycloak.onTokenExpired = () => {};
};
}, [initialized, keycloak]);
return (
<MyPreferedRouter>
<Switch />
</MyPreferedRouter>
);
}
Is working on #react-keycloak/ssr and i used this implementation with redux to have the token in the store
Don't forget to adapt keycloak.updateToken(600);
600 is number of seconds your minValidity
I made some investigation in this point because I couldn't get new token by refresh token, this is what worked with me
I used Keycloak end point:
https://<yourAuthLink>/auth/realms/<relmName>/protocol/openid-connect/token
with headers object
headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
and the body will be like that :
body: "client_id"=<clientId>&"grant_type"="refresh_token"&"refresh_token"=<refreshToken>&"client_secret"=<clientSecret>
this will return response which has access_token which you use as token and refresh_token to use it again before expiration time
it is useful link for this type of endpoint and headers
We use this flow
useEffect(() => {
dispatch(keycloak.token);
// and then save it to localStorage
}, [keycloak.token]);
useEffect(() => {
// jast in case
if(!initialized)
return;
if(!keycloak.authenticated)
return;
keycloak.onTokenExpired = () => {
keycloak.updateToken(50);
};
}, [keycloak.authenticated]);
But here I have a question: if the user sleep for a long time and then need to do some API request, so here I have to ask for refreshed token before request
but useKeycloak hook doesn't work in this case

Resources