I am trying to send param from screen A to screen B like this:
this is what I am trying so far:
await Auth.signIn(email, password)
.then((user) => {
navigation.navigate('SingInConfirm', {user: user});
})
And on my screen B, I want to recibe user params like this..
const SingInConfirmation = ({ route, signIn: signInCb }) => {
const { user } = route.params;
But I am getting this error: TypeError: undefined is not an object (evaluating 'route.params')
and this is my StackNavigator
const AuthStack = createStackNavigator();
const AuthModalStack = createStackNavigator();
const AuthNavigator = ({ signIn }) => (
<AuthModalStack.Navigator mode="modal" headerMode="none">
<AuthModalStack.Screen name="AuthPages">
{() => (
<AuthStack.Navigator>
<AuthStack.Screen name="SignIn" component={SignInScreen} />
</AuthStack.Navigator>
)}
</AuthModalStack.Screen>
<AuthModalStack.Screen name="SingInConfirm">
{({ navigation }) => <SingInConformation signIn={signIn} navigation={navigation} />}
</AuthModalStack.Screen>
</AuthModalStack.Navigator>
);
export default AuthNavigator;
Why I can't recibe route params on my screen B?
SingInConformation component doesn't receive route in this line as a prop, you need to provide it, change this line from :
{({ navigation }) => <SingInConformation signIn={signIn} navigation={navigation} />}
To this:
{({ route, navigation }) => <SingInConformation signIn={signIn} navigation={navigation} route={route} />}
Related
If I set in context provider sample data, I see this data in all nested components.
But I need login to the account and in response, I get data about user for set in the global context and use in all components.
context/AuthProvider.tsx
const AuthContext = createContext<any>({});
export const AuthProvider = ({ children }: any) => {
const [auth, setAuth] = useState({});
return (
<>
<AuthContext.Provider value={{ auth, setAuth }}>{children}</AuthContext.Provider>
</>
);
};
hooks/useAuth.ts
const useAuth = () => {
return useContext(AuthContext);
};
export default useAuth;
index.tsx
import { AuthProvider } from './context/AuthProvider';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>
);
I have App with BrowserRouter logic for not logged users redirect to login. If logged, so go to the Homepage.
components/App/App.tsx
const AppContainer: FC<any> = () => {
const { token } = useToken();
return (
<>
<div className={'wrapper'}>
<BrowserRouter>
{!token ? <LoggedOutRoutes /> : <LoggedInRoutes />}
</BrowserRouter>
</div>
</>
);
};
const LoggedOutRoutes: FC<any> = () => (
<Switch>
<Route path="/" exact={true}>
<Login />
</Route>
<Redirect from={'*'} to={'/'} />
</Switch>
);
const LoggedInRoutes: FC = () => (
<Switch>
<Route path="/" exact={true} component={Homepage} />
</Switch>
);
In login component sending request with data and I getting access_token and user data. Now I need set user data in useAuth hook.
const Login: FC<any> = () => {
const { setToken } = useToken();
const { setAuth } = useAuth()
const handleSubmit = async (event: any) => {
event.preventDefault();
const res = await API.login({
login,
password
});
const { access_token, user } = res;
setToken(access_token);
setAuth(user);
window.location.reload();
};
return (
<form onClick={handleSubmit}>
// ...There i have submit form, not interesting
</form>
);
};
After reloading the page, my page will be Homepage where I won't use my context data from the provider but I have an empty object, why?
The problem is window.location.reload. Any SPA will not retain data after a page refresh by default.
Now if you still want to persist that information even after page reload, i recommend to persist that info in localStorage. So something like this should work.
export const AuthProvider = ({ children }: any) => {
const [auth, setAuth] = useState(localStorage.get('some-key') || {});
const updateAuth = (auth) => {
localStorage.set('some-key', auth);
setAuth(auth);
}
return (
<>
<AuthContext.Provider value={{ auth, updateAuth }}>{children}</AuthContext.Provider>
</>
);
};
this is the scenario:
In Home Route, there is a dropdown which contains SubSystems and based on each subSystem menu items are different. So, when I change subSystem, Menu Items of drawer should update.
I have implemented my drawer to get dynamic items like below code. But, the problem is that when then SubSystems are loaded I go to get the menu items after that I update the menuData state the whole component recompiles and it falls into a loop.
How Can I Overcome This Problem ?
MY CODE
StackNavigationManager.js
import {AuthContext} from './context';
const StackNavigationManager = () => {
const [menuData, setMenuData] = React.useState([]);
const authContext = React.useMemo(() => ({
validateMenuData: menuData => {
setMenuData(menuData);
},
}));
const DrawerScreen = ({route, navigation}) => (
<Drawer.Navigator
drawerContent={props => <CustomDrawerContent {...props} />}>
<Drawer.Screen
name="HomeDrawer"
component={Home} />
</Drawer.Navigator>
);
function CustomDrawerContent(props) {
return (
<SafeAreaView style={{flex: 1}}>
<DrawerItemList {...props} />
<FlatList
data={menuData}
keyExtractor={data => data.id.toString()}
renderItem={({item}) => {
return (
<DrawerItem
label={item.name}
onPress={() => alert(item.id)}></DrawerItem>
);
}}
/>
</SafeAreaView>
);
}
return (<AuthContext.Provider value={authContext}>
<NavigationContainer>
<Stack.Navigator
initialRouteName={initialRoute}>
<Stack.Screen
name="Home"
component={DrawerScreen}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>);
Context.js
import React from 'react';
export const AuthContext = React.createContext();
Home.js
import {AuthContext} from './context';
const home = ({navigation}) => {
const {validateMenuData} = React.useContext(AuthContext);
React.useEffect(() => {
console.log('MOUNTED EFFECT');
getSubSystems();
return () => {
isMounted = false;
console.log('UNMOUNTED EFFECT');
};
}, []);
const getSubSystems = async () => {
const subSystemModel = {
isActive: true,
isVisible: true,
};
fetchSubSystemData(
subSystemModel,
response => {
if (response.list !== null) {
let listData = [...response.list];
fetchMenuAction(
listData[0].id,
response2 => {
validateMenuData([...response2.list]);
},
);
}
},
() => {},
);
};
I'm using react navigation and Firebase for authentication for a user to move to different screens in my react navigation app.
I created a logout function that signs the user out and takes them to the login screen. However on the upper left corner my app gives the user the option to go back into app without the need to log back in with button labelled as the name of the screen they just logged out of. Is there a way to disable this?
Here is my logout screen :
export default function ProductScreen({ navigation }) {
const logOutPress = () => {
try {
auth()
.signOut()
.then(() => {
navigation.navigate("Login"), alert("You have signed out");
});
} catch (error) {
console.log("err", error);
}
};
return (
<TouchableOpacity onPress={() => logOutPress()}>
<Text style={styles.buttonText}>Log Out</Text>
</TouchableOpacity>
);
}
Also here is my app.js where I house my react navigation and Firebase :
const Stack = createStackNavigator();
export default function App() {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
useEffect(() => {
const usersRef = firebase.firestore().collection("users");
firebase.auth().onAuthStateChanged((user) => {
if (user) {
usersRef
.doc(user.uid)
.get()
.then((document) => {
const userData = document.data();
setLoading(false);
setUser(userData);
})
.catch((error) => {
setLoading(false);
});
} else {
setLoading(false);
}
});
}, []);
if (loading) {
return <></>;
}
return (
<NavigationContainer>
<Stack.Navigator>
{user ? (
<Stack.Screen name="Products">
{(props) => <ProductScreen {...props} extraData={user} />}
</Stack.Screen>
) : (
<>
<Stack.Screen name="Login" component={LoginScreen} />
</>
)}
<Stack.Screen name="Registration" component={RegistrationScreen} />
<Stack.Screen name="Payment" component={PaymentScreen} />
<Stack.Screen name="Lawn Care" component={LawnCareScreen} />
<Stack.Screen name="Reset Password" component={ResetPasswordScreen} />
<Stack.Screen name="Car Detail" component={CarDetailScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
To disable user from navigating back to the previous screen after logout you need to clear the stack of the stack navigator in the Product Screen . I have provided the code sample below
import { StackActions, NavigationActions } from "react-navigation";
export default function ProductScreen({ navigation }) {
const logOutPress = () => {
try {
auth()
.signOut()
.then(() => {
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: "Login" })],
});
navigation.dispatch(resetAction);
alert("You have signed out");
});
} catch (error) {
console.log("err", error);
}
};
return (
<TouchableOpacity onPress={() => logOutPress()}>
<Text style={styles.buttonText}>Log Out</Text>
</TouchableOpacity>
);
}
i use this code in the index.js to logout :
<Stack.Screen name="Main" component={Main} options={({ navigation,route }) => ({
title: '', headerLeft: () => (<TouchableOpacity onPress={() => (_singout())}
style={{ margin: 16, marginTop: Platform.OS == 'ios' ? StatusBar.currentHeight : 0 }}>
<Image resizeMode="contain" source={mylogoutImage} style={{ marginTop: 20 }} />
</TouchableOpacity>),
})} />
and in _singout() function clear my asyncStorage.
I hope to help you.
I am using React Navigation v5. I have an auth setup like their example in the docs. My problem is that I cannot work out how to pass error messages back to the component when there is a problem with signup/in.
The validation is done on the server and returned in the response object.
Currently the page just sits there with the loading spinner spinning while it waits for a response which it never receives.
Im going to try to cut down the amount of code I include here because it is basically a copy paste job from the docs
// Login.tsx
const loginUser = async () => {
setIsLoading(true);
signIn({ email, password });
};
return (
<View>
<TextInput
label='Email'
onChangeText={(text: string) => setEmail(text)}
value={email}
/>
<TextInput
label='Password'
onChangeText={(text: string) => setPassword(text)}
value={password}
secureTextEntry
/>
{errorMessage && <Text>{errorMessage}</Text>}
<Button mode='contained' disabled={showButton()} onPress={loginUser}>
{isLoading ? <ActivityIndicator color='white' /> : 'Submit'}
</Button>
<Button mode='outlined' onPress={() => navigation.navigate('SignUp')}>
Go to sign up
</Button>
</View>
);
// AuthStackNavigator.tsx
import React from 'react';
import { createStackNavigator } from '#react-navigation/stack';
import SignupScreen from '../Screens/SignupScreen';
import LoginScreen from '../Screens/LoginScreen';
const AuthStack = createStackNavigator();
export const AuthStackScreen = () => {
return (
<AuthStack.Navigator screenOptions={{ headerLeft: null }}>
<AuthStack.Screen name='SignUp' component={SignupScreen} />
<AuthStack.Screen name='SignIn' component={LoginScreen} />
</AuthStack.Navigator>
);
};
export default AuthStackScreen;
// Navigation.tsx
const authContext = useMemo(
() => ({
signIn: async ({ email, password }: SignInTypes) => {
const userData = {
email,
password,
};
API.post
.userLogin(userData)
.then((result) => {
if (
result.data.status === 'fail' ||
result.data.status === 'error'
) {
setIsLoading(false);
// I NEED TO DO SOMETHING HERE I THINK BUT ICANT WORK OUT WHAT I NEED TO DO
}
const accessToken = [
'accessToken',
result.data.data.tokens.accessToken.jwtToken,
];
const refreshToken = [
'refreshToken',
result.data.data.tokens.refreshToken.token,
];
addUser({
id: result.data.data.user._id,
email: result.data.data.user.email,
profileName: result.data.data.user.profileName,
balance: result.data.data.user.balance,
});
try {
AsyncStorage.multiSet([accessToken, refreshToken]);
setIsLoading(false);
return dispatch({ type: 'SIGN_IN', token: accessToken[1] });
} catch (e) {
return console.log('ASYNC STORAGE ERROR', e);
}
})
.catch((e) => {
console.log('Sign in error', e);
});
}
}),
[],
);
const navigationOptions = () => {
if (isLoading) {
return <LoadingScreen />;
}
return !state.userToken ? (
<AuthStackScreen />
) : (
<AppStackScreen />
);
};
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>{navigationOptions()}</NavigationContainer>
</AuthContext.Provider>
);
Create two separate Stack navigators. AuthStack and MainStack. Like you have.
Conditionally render those stacks based on auth Status. Like you have.
// AuthStack containing the Login and SignUp screens.
export const AuthStack = () => {
return (
<Stack.Navigator headerMode="none">
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</Stack.Navigator>
);
};
// Main Stack that contains The rest of the screens and other navigation components.
export const MainStack = () => {
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={TabNavigator} />
<Stack.Screen name="Ledger" component={LedgerScreen} />
<Stack.Screen name="CustomerProfile" component={CustomerProfileScreen} />
</Stack.Navigator>
// App.tsx or which ever file you choose to wrap your Stacks in the Navigation container.
const App = (props) => {
const isAuth = useSelector((state) => !!state.auth.token); // Comes from the redux store
return (
<NavigationContainer>
{isAuth && <MainStack />}
{!isAuth && <AuthStack />}
</NavigationContainer>
);
};
export default App
Here, I'm using react-redux to get the isAuth variable that holds the token which verifies if the user is authenticated or not. But, you can use the useContext hook to achieve this as well.
Based on the user's login status the appropriate stacks are rendered.
You can now make your calls to API (the ones you are making in Navigation.tsx) from within the particular screen components itself inside the useEffect and useCallback hooks and get back the responses within the same component, which can include error messages.
Store those error messages in useState or useReducer hooks and display these errors to the user under the appropriate TextInputs or use an Alert Box to display them.
There is no need to pass props between the screen components and React Navigation 5 to get the error messages, if you make your api calls in the screen component itself.
I have a component called admin it is a form that will redirect me to another page rendering not working though.
Router main
const globalState = {
isAuthed: false,
token: null,
};
export const AuthContext = React.createContext(globalState);
function App() {
const [currentUser, setCurrentUser] = useState(globalState)
return (
<AuthContext.Provider value={[currentUser, setCurrentUser]}>
<Router>
<Switch>
<Route exact path="/admin" component={Admin} />
<Route exact path="/admin-panel" component={Pannel} />
</Switch>
</Router>
</AuthContext.Provider>
)
}
export default App;
admin component
const LoginForm = () => {
const [state, setState] = useContext(AuthContext)
const login = (state) => {
const user = document.getElementById('user').value;
const pass = document.getElementById('pass').value;
const request = {
user,
pass
}
console.log(request)
fetch('/api/admin', {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if(res.auth){
valid(5000,"Login Success. Redirecting in 3 second")
setTimeout(() =>{
setState({isAuthed: res.auth, token: res.key})
}, 3000)
}
else{
warn(5000,res.message)
}
})
}
return(
<div style={css}>
<ToastContainer />
{(state && state.isAuthed)? <Redirect to='/adming-panel'/>: false}
<h1 style={{color: "teal"}}>Admin Panel</h1>
<Form id="login-form" size='large' style={{backgroundColor: "white"}}>
<Segment stacked>
<Form.Input id="user" fluid icon='user' iconPosition='left' placeholder='E-mail address' />
<Form.Input
fluid
icon='lock'
iconPosition='left'
placeholder='Password'
type='password'
id="pass"
/>
<Button onClick={() => login(state)} color='teal' fluid size='large'>
Login
</Button>
</Segment>
</Form>
</div>
)
}
export default LoginForm
the new page that I want to render
const Pannel = () => {
const [state, setState] = useContext(AuthContext)
return (
<div>
{(!state || !state.isAuthed)? <Redirect to='/adming-panel'/>: false}
Secret Page
</div>
)
}
export default Pannel
All the answers that I searched for. Was to put the exact keyword before the path but still, the component won't render only an empty white screen appears and no errors on console or backend console.
<Route exact path="/admin-panel" component={Pannel} />
Spot the key difference
<Redirect to='/adming-panel'/>
You are welcome.