How can I replace screen with React Navigation for React Native
Now I'm a newbie I can't understand about getStateForAction
now I have params on Screen 1 and Navigate to Screen 2 with passing params username with basic Navigate it's nested screen 1 > 2 on stack
But I need to replace screen 1 with screen 2 after screen 2 it's on active (replace like ActionConst.REPLACE on router flux) and sending params on a new way
Can someone guide me thank you.
screen 1
onPress = () => {
this.props.navigation.navigate('Sc2', {username: this.state.username});
}
Screen 2
componentWillMount() {
const {state} = this.props.navigation;
console.log(state.params.username);
}
---------------
Router
export const LoginNavStack = StackNavigator({
Sc1: {
screen: Sc1
},
Sc2: {
screen: Sc2,
},
});
Incase anyone's still figuring out how to replace screens in React Native and is using react-navigation, here it is:
import { StackActions } from '#react-navigation/native';
navigation.dispatch(
StackActions.replace('Profile', {
user: 'jane',
})
);
For more information refer: https://reactnavigation.org/docs/stack-actions/#replace
Use this instead of navigation.replace
navigation.reset({
index: 0,
routes: [{ name: 'Profile' }],
});
You can use navigation.replace
Use
this.props.navigation.replace('Sc2');
Instead of
this.props.navigation.navigate('Sc2', {username: this.state.username});
You can find more from here https://reactnavigation.org/docs/en/navigation-key.html
"Replace" is only available in stack navigation
replace typed:
import { useNavigation } from '#react-navigation/core'
import { StackNavigationProp } from '#react-navigation/stack'
type YourNavigatorParamList = {
CurrentScreen: undefined
DifferentScreen: { username: string }
}
const navigation = useNavigation<StackNavigationProp<YourNavigatorParamList, 'CurrentScreen'>>()
navigation.replace('DifferentScreen', { username: 'user01' })
https://reactnavigation.org/docs/navigation-prop/#navigator-dependent-functions
https://reactnavigation.org/docs/typescript#annotating-usenavigation
This is working for me.
import { View, Text, Button } from 'react-native'
const Home = (nav:any) => {
const goToLogin = () => {
nav.navigation.replace('Login');
}
return (
<View>
<Text>Home</Text>
<Button title='Go To Login' onPress={goToLogin} />
</View>
)
}
export default Home;
Related
I am basically trying to intercept route changes. Maybe something equivalent of vue's beforeEach in React Router v6 could be useful as React Router v.6 does not include usePrompt.
BEFORE each route change I want to do some logic - the logic might need to interrupt or even change the end route based on the result.
I have searched around but I really can't find something that solves this specific problem.
Thanks in advance.
Currently they have removed the usePrompt from the react-router v6.
I found a solution from ui.dev and added TypeScript support, and am now using that until the react-router will bring back the usePrompt/useBlocker hooks
import { History, Transition } from 'history';
import { useCallback, useContext, useEffect } from "react";
import { Navigator } from 'react-router';
import { UNSAFE_NavigationContext as NavigationContext } from "react-router-dom";
type ExtendNavigator = Navigator & Pick<History, "block">;
export function useBlocker(blocker: (tx: Transition) => void, when = true) {
const { navigator } = useContext(NavigationContext);
useEffect(() => {
if (!when) return;
const unblock = (navigator as ExtendNavigator).block((tx) => {
const autoUnblockingTx = {
...tx,
retry() {
unblock();
tx.retry();
},
};
blocker(autoUnblockingTx);
});
return unblock;
}, [navigator, blocker, when]);
}
export default function usePrompt(message: string, when = true) {
const blocker = useCallback((tx: Transition) => {
if (window.confirm(message)) tx.retry();
}, [message]);
useBlocker(blocker, when);
}
This can then be used in any view/component where you would like a "A you sure you want to leave?"-message displayed when the condition is true.
usePrompt("Do you want to leave?", isFormDirty());
Yes usePrompt and useBlock has been removed, but you can achieve same thing using history.block, here is the working example for blocking navigation using history.block with custom modal in React Router Dom V5
import { useHistory } from "react-router-dom";
import { UnregisterCallback } from "history";
...
type Prop = {
verify?: {
blockRoute?: (nextRoute: string) => boolean;
}
};
...
// in the component where you want to show confirmation modal on any nav change
const history = useHistory();
const unblock = useRef<UnregisterCallback>();
const onConfirmExit = () => {
/**
* if user confirms to exit, we can allow the navigation
*/
// Unblock the navigation.
unblock?.current?.();
// Proceed with the blocked navigation
goBack();
};
useEffect(() => {
/**
* Block navigation and register a callback that
* fires when a navigation attempt is blocked.
*/
unblock.current = history.block(({ pathname: to }) => {
/**
* Simply allow the transition to pass immediately,
* if user does not want to verify the navigate away action,
* or if user is allowed to navigate to next route without blocking.
*/
if (!verify || !verify.blockRoute?.(to)) return undefined;
/**
* Navigation was blocked! Let's show a confirmation dialog
* so the user can decide if they actually want to navigate
* away and discard changes they've made in the current page.
*/
showConfirmationModal();
// prevent navigation
return false;
});
// just in case theres an unmount we can unblock if it exists
return unblock.current;
}, [history]);
Here is a JS example of the react-route-dom v6 usePrompt if you're not using TS.
import { useContext, useEffect, useCallback } from 'react';
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
export function useBlocker( blocker, when = true ) {
const { navigator } = useContext( NavigationContext );
useEffect( () => {
if ( ! when ) return;
const unblock = navigator.block( ( tx ) => {
const autoUnblockingTx = {
...tx,
retry() {
unblock();
tx.retry();
},
};
blocker( autoUnblockingTx );
} );
return unblock;
}, [ navigator, blocker, when ] );
}
export function usePrompt( message, when = true ) {
const blocker = useCallback(
( tx ) => {
// eslint-disable-next-line no-alert
if ( window.confirm( message ) ) tx.retry();
},
[ message ]
);
useBlocker( blocker, when );
}
Then the implementation would be...
const MyComponent = () => {
const formIsDirty = true; // Condition to trigger the prompt.
usePrompt( 'Leave screen?', formIsDirty );
return (
<div>Hello world</div>
);
};
Here's the article with the example
I am getting issues while rendering contact data.
Here the case is when I click the continue button in my app it triggers the checkoutContinuedHandler() function that results in a change of URL but the ContactData component is not rendered and my CheckoutSummary component also vanishes as I am rendering it on the same page.
I Checked twice that export is done and there is no spelling mistakes.
I tried different solutions from the stack and discussed them with my mate still the issue is on...
import React, { Component } from "react";
import { Route } from "react-router-dom";
import CheckoutSummary from "../../components/Order/CheckoutSummary/CheckoutSummary";
import ContactData from "./ContactData/ContactData";
class Checkout extends Component {
state = {
ingredients: {
salad: 1,
meat: 1,
cheese: 1,
bacon: 1,
},
};
componentDidMount() {
const query = new URLSearchParams(this.props.location.search);
const ingredients = {};
for (let param of query.entries()) {
// ['salad','1']
ingredients[param[0]] = +param[1];
}
this.setState({ ingredients: ingredients });
}
checkoutCancelledHandler = () => {
this.props.history.goBack();
};
checkoutContinuedHandler = () => {
this.props.history.replace("/checkout/contact-data");
console.log(this);
};
render() {
return (
<div>
<CheckoutSummary
ingredients={this.state.ingredients}
checkoutCancelled={this.checkoutCancelledHandler}
checkoutContinued={this.checkoutContinuedHandler}
/>
<Route
path={this.props.match.path + "/contact-data"}
component={ContactData}
/>
</div>
);
}
}
export default Checkout;
I am using this https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html to access my navigation from any source, my file look as follow:
import { createRef } from 'react';
export const navigationRef = createRef();
export function navigate(name, params) {
return navigationRef.current?.navigate(name, params);
}
export function goBack() {
return navigationRef.current?.goBack();
}
export function getRootState() {
return navigationRef.current?.getRootState();
}
This is perfect for my #navigation/drawer, which is outside my stack navigation.
Only one problem the last method is not synchronized and I want to have an active state on my item menu that is the current route.
How is that possible with react navigation 5?
I am using the following approach to get the current route name in react-navigation v5.
https://reactnavigation.org/docs/navigation-prop/#dangerouslygetstate
const {index, routes} = this.props.navigation.dangerouslyGetState();
const currentRoute = routes[index].name;
console.log('current screen', currentRoute);
The NavigationContainer has onStateChange prop, useful for this case, check react-navigation docs Screen Tracking for analytics and if you need access without navigation prop see Navigating without the navigation prop
I share the code to get only active routeName
function App(){
const routeNameRef = React.useRef();
// Gets the current screen from navigation state
const getActiveRouteName = (state)=> {
const route = state.routes[state?.index || 0];
if (route.state) {
// Dive into nested navigators
return getActiveRouteName(route.state);
}
return route.name;
};
return (<NavigationContainer
onStateChange={(state) => {
if (!state) return;
//#ts-ignore
routeNameRef.current = getActiveRouteName(state);
}}
>
...
</NavigationContainer>)
}
If you want to know the current screen from a component you can also use this:
export const HomeScreen = ({ navigation, route }) => {
console.log(route.name);
return (
{...}
);
};
It is possible to get this from the navigationRef attached to the navigation container. Where navigationRef is a ref.
export const navigationRef = React.createRef()
and
<NavigationContainer
ref={navigationRef}
>
<Navigator />
</NavigationContainer>
Then use: const currentRouteName = navigationRef.current.getCurrentRoute().name to get the current route name.
Alternatively in a functional component you can useRef const navigationRef = React.useRef()
There is a util function called getFocusedRouteNameFromRoute(route) which the docs recommends.
BUT - it seems its working only for child screen, so I defined the following function to get the active route name:
const getCurrentRouteName = (navigation, route) => {
if (route.state)
return getFocusedRouteNameFromRoute(route);
return route.name;
};
This works for me. In navigationRef.js
let navigator;
export const setNavigator = (nav) => {
navigator = nav;
};
export const getCurrentRoute = () => {
const route = navigator.getCurrentRoute();
return route.name;
};
This can be referred from any source like this
import { getCurrentRoute } from '../navigationRef';
const currentScene = getCurrentRoute();
I am using Machine Learning Face Detection API from a website called "clarifai", Though in my react App.js folder there is an issue, and the error says, "TypeError: this.setstate is not a function ". I am attaching a link to an image of my React localhost.React localhost screenshot
Actually, I am a beginner in react and trying to build a basic react website and embedding Machine learning API to detect faces when a user tries to enter an image link. Any help would be much appreciated !
import React, { Component } from 'react';
import Navigation from './Components/Navigation/Navigation';
import FaceRecognition from './Components/FaceRecognition/FaceRecognition';
import Clarifai from 'clarifai';
import Logo from './Components/Logo/Logo';
import ImageLinkForm from './Components/ImageLinkForm/ImageLinkForm';
import Rank from './Components/Rank/Rank';
import Particles from 'react-particles-js';
import './App.css';
const app = new Clarifai.App({
apiKey: 'API_KEY'
});
const particlesOptions = {
particles: {
number: {
value:100,
density: {
enable: true,
value_area:800
}
}
}
}
class App extends Component {
constructor() {
super();
this.state = {
input:'',
imageUrl:'',
box: {},
}
}
calculateFaceLocation = (data) =>{
const clarifaiFace = data.outputs[0].data.regions[0].region_info.bounding_box
const image = document.getElementById('inputimage');
const width = Number(image.width);
const height = Number(image.height);
return{
leftCol: clarifaiFace.left_col * width,
topRow : clarifaiFace.top_row * height,
rightCol : width - (clarifaiFace.right_col * width),
bottomRow : height - (clarifaiFace.bottom_row * height)
}
}
displayFaceBox = (box) => {
console.log(box);
this.setState = ({box: box})
}
onInputChange = (event) => {
this.setState({input: event.target.value});
}
onButtonSubmit = () => {
this.setState({imageUrl: this.state.input});
app.models
.predict(Clarifai.FACE_DETECT_MODEL,
this.state.input)
.then(response => this.displayFaceBox(this.calculateFaceLocation(response)))
.catch(err => console.log(err));
}
render() {
return (
<div className="App">
<Particles className='particles'
params={particlesOptions}
/>
<Navigation />
<Logo />
<Rank />
<ImageLinkForm
onInputChange={this.onInputChange}
onButtonSubmit={this.onButtonSubmit}/>
<FaceRecognition box={this.state.box} imageUrl={this.state.imageUrl}/>
</div>
);
}
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Call the setState function, don't change its definition
displayFaceBox = (box) => {
console.log(box);
// you can also use object short hand, instead of {box: box}
this.setState({box});
}
Make following changes in your code and see if it works.
Change
this.setState = ({box: box})
To
this.setState({box})
I am just getting stuck into react-native and need some help navigating to a protected screen when a token is found. Where should I look for a token on app load? And how can I navigate the user once without calling navigate multiple times? The problem I have is I am checking for a token on component mount, which is nested inside a stack. If I navigate to another part of the stack, the function is called again and I am unable to navigate. I can retrieve the token outside of the stack, but then I am having trouble navigating, as I need to pass props.navigate within a screen component. What is the recommended approach to finding a token, and making a navigation?
App.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import store from './store';
import RootContainer from './screens/RootContainer';
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<RootContainer />
</Provider>
);
}
}
RootContainer.js
...
render() {
const MainNavigator = createBottomTabNavigator({
welcome: { screen: WelcomeScreen },
auth: { screen: AuthScreen },
main: {
screen: createBottomTabNavigator({
map: { screen: MapScreen },
deck: { screen: DeckScreen },
review: {
screen: createStackNavigator({
review: { screen: ReviewScreen },
settings: { screen: SettingsScreen }
})
}
})
}
// Route Configuration for Initial Tab Navigator
}, {
// do not instantly render all screens
lazy: true,
navigationOptions: {
tabBarVisible: false
}
});
return (
<View style={styles.container}>
<MainNavigator />
</View>
);
}
}
WelcomeScreen.js
...
componentDidMount(){
this.props.checkForToken(); // async method
}
// Async Action
export const checkForToken = () => async dispatch => {
console.log("action - does token exist ?");
let t = await AsyncStorage.getItem("jwt");
if (t) {
console.log("action - token exists");
// Dispatch an action, login success
dispatch({ type: LOGIN_SUCCESS, payload: t });
} else {
return null;
}
}
// WelcomeScreen.js continued
componentWillRecieveProps(nextProps){
this.authComplete(nextProps);
}
authComplete(props){
if(props.token){
props.navigation.navigate('map'); // called again and again when I try to navigate from within the Bottom Tab Bar Component
}
}
render(){
if(this.props.appLoading){ // default True
return ( <ActivityIndicator />);
}
return ( <Text>WelcomeScreen</Text> );
}
const mapStateToProps = state => {
return {
token: state.auth.token,
appLoading: state.auth.appLoading // default True
};
};
export default connect(mapStateToProps, actions)(WelcomeScreen);
I would suggest, not to store navigation state in redux.
Just navigate when you found a token or the user logged in.
If you still want to use redux or simply want to react on props changes, then the way is to use some Redirect Component, and render it only when the token changed from nothing to something. You could read about such implementation from react-router here. I think there is no such implementation for React Navigation.
When you are using React Navigation, then I would suggest to look into the docs, because I think it solves the problem you have.
https://reactnavigation.org/docs/en/auth-flow.html