How to pass data back to previous screen with React Navigation 6. React Native - react-native

I am trying to pass the selected data i'm fetching from my rest api back to the previous screen. I followed the docs, but it's not working. Not sure what I am doing wrong.
I am updating the state w/ the selected data, and know it's working bc i am printing it out in console successfully.
This is the button that calls the function to navigate back, passing the state w/ selected data in Screen B:
<Pressable onPress={() => postSelected(selectedData)} >
<Text style={{fontSize: 13, color: 'white', fontWeight: '700', paddingRight: 5}}>{'Select'}</Text>
</Pressable>
This is the function (Screen B):
const postSelected = (selectedData) => {
navigation.navigate({
name: 'CreatePost',
params: { postData: selectedData },
merge: true
});
}
In Screen A, I have a useEffect that listens for the selected data from Screen B:
useEffect(() => {
if (route.params?.postData) {
console.log('Sent');
}
}, [route.params?.postData]);
But it's not receiving the data.
I was following these docs: https://reactnavigation.org/docs/params/
Appreciate any help!

You can use props.navigation.getParam("postData") Method to get params from navigation

The solution that worked for me and follows the best practices set by react-navigation is as follows
const MainScreen = ({ navigation }) => {
// Here is where the returned data is detected
React.useEffect(() => {
if (route.params?.post) {
// Do something with the data
}
}, [route.params?.post])
const openChildScreen = () => {
navigation.navigate('NewPostScreen')
}
}
// ---------------------
const NewPostScreen = ({ navigation }) => {
const goBackToMain = () => {
// This is the data to be returned to the main screen
const post = {
// ...
}
navigation.navigate('MainScreen', { post });
}
}

Related

React Native inactivity logout

Hello i want to make an inactivity log out for my app, so if the user doesn't do anything on the app for 3 minutes, the app will comeback to the login screen.
I'm using expo, react native navigation V6, and functional components.
i haven't been able to figure how to do it. please help.
I think i was able to do that on an app that i made 1 year ago, i think this code can you help you.
const ManageExpenses = ({ route, navigation }) => {
const [time, setTime] = useState(0);
useEffect(() => {
let mounted = true;
if (mounted) {
tick();
}
return () => mounted = false;
}, []);
function tick() {
let timer = setInterval(() => {
setTime((prevTime) => (prevTime = prevTime + 1));
}, 1000);
}
if (time >= 10) {
navigation.goBack();
}
function pressHandler() {
setTime(0)
}
return (
<Pressable onPress={pressHandler} style={styles.container}>
</Pressable>
);
export default ManageExpenses;
const styles = StyleSheet.create({
container: {
flex: 1,
}
})
I created a Pressable component around the entire screen and it redefine the time state when the user press the screen. I hope this will help you!

Pass useAnimatedGestureHandler via forwardRef

I'm about to swap the old React Native Animated library with the new React Native Reanimated one to gain performance issues but I have encountered one problem I could not solve.
In all examples I found online, I saw that the GestureHandler, created with useAnimatedGestureHandler, is in the same component as the Animated.View. In reality that is sometimes not possible.
In my previous app, I just pass the GestureHandler object to the component via forwardRef but it seems React Native Reanimated is not able to do that. I don't know whether I have a syntax error or it is just a bug.
const App = () => {
const handlerRef = useAnimatedRef();
const y = useSharedValue(0);
handlerRef.current = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.startY = y.value;
},
onActive: ({translationX, translationY}, ctx) => {
y.value = translationY;
},
onEnd: () => {},
});
const animatedStyles = useAnimatedStyle(() => ({transform: [{translateY: withSpring(y.value)}]}));
const UsingHandlerDirect = () => (
<PanGestureHandler onGestureEvent={handlerRef.current} >
<Animated.View style={[styles.blueBox, animatedStyles]} />
</PanGestureHandler>
)
const UsingHandlerForwardRef = forwardRef(({animatedStyles}, ref) => (
<PanGestureHandler onGestureEvent={ref?.handlerRef?.current}>
<Animated.View style={[styles.redBox, animatedStyles]} />
</PanGestureHandler>
));
return (
<SafeAreaView>
<View style={styles.container}>
<UsingHandlerForwardRef ref={handlerRef} animatedStyles={animatedStyles}/>
<UsingHandlerDirect />
</View>
</SafeAreaView>
);
}
I have saved the GestureHandler in a useAnimatedRef handlerRef.current = useAnimatedGestureHandler({}) to make things more representable. Then I pass the the ref directly into the PanGestureHandler of the UsingHandlerDirect component. The result is that when I drag the blue box the box will follow the handler. So this version works.
But as soon as I pass the handlerRef to the UsingHandlerForwardRef component non of the gesture events get fired. I would expect that when I drag the red box will also follow the handler but it doesn't
Has someone an idea whether it's me or it's a bug in the library?
Cheers
I have given up on the idea to pass a ref around instead, I created a hook that connects both components with each other via context.
I created a simple hook
import { useSharedValue } from 'react-native-reanimated';
const useAppState = () => {
const sharedXValue = useSharedValue(0);
return {
sharedXValue,
};
};
export default useAppState;
that holds the shared value using useSharedValue from reanimated 2
The child component uses this value in the gestureHandler like that
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.startX = sharedXValue.value;
},
onActive: (event, ctx) => {
sharedXValue.value = ctx.startX + event.translationX;
},
onEnd: (_) => {
sharedXValue.value = withSpring(0);
},
});
and the Parent just consumes the hook value
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: -sharedXValue.value,
},
],
};
});
I have created a workable Snack which contains the 2 components - a Child with a blue box and a Parent with a red box

React Native add data to Async Storage

I'm developing application about travelling, so I have a map with places to travel and I want to implement adding to favourites functionality. When the user clicks on the marker on the map I show him modal window with all the information about the place and there I have icon-button "add-to-favorites". When user clicked on this "add-to-favourites" icon-button I want to save the place to the AsyncStorage, rerender icon to "remove-from-favourites" then if the place in favouries and user clicks on "remove-from-favorites" then remove place from AsyncStorage and rerender icon the add-to-favorites. I don't really understand how to do it in a right way in functional component. Give me an example please.
What I tried:
My ModalWindow code:
const [favourite, setFavourite] = useState(false);
const onHeartPress = async () => {
setFavourite(prev => !prev);
await storage.save({
key: 'markers',
data: {
marker: marker
},
expires: null
})
<TouchableOpacity onPress={() => onHeartPress()}>
<View style={{flexDirection: "row", alignItems: "center"}}>
{favourite ? <Ionicons size={height/20} name={'heart-dislike'} /> : <Ionicons size={height/20} name={'heart'} />}
</View>
</TouchableOpacity>
It's not difficult to understand how to add to the storage and then load from the storage but how to render the icon correctly like heart/dislike heart because now with this code when I press on button icon in one place in the other places it also changes, because favourite becomes true so I want to understand how in the correct way change icon only for place where user clicked on icon.
You can follow this code to achieve this functionality:
const STORAGE_KEY = '#icon_name'
// Store data
const saveData = async () => {
try {
await AsyncStorage.setItem(STORAGE_KEY, age)
alert('Data successfully saved')
} catch (e) {
alert('Failed to save the data to the storage')
}
}
// fetch data
const readData = async () => {
try {
const userAge = await AsyncStorage.getItem(STORAGE_KEY)
if (userAge !== null) {
setAge(userAge)
}
} catch (e) {
alert('Failed to fetch the data from storage')
}
}
//remove data
try {
await AsyncStorage.removeItem(STORAGE_KEY);
console.log('Data removed')
}
catch(exception) {
console.log(exception)
}

How to update the header while the component is still rendered using React Navigation?

I'm writing a React Native app and I'm using React Navigation (V2) with it. I want to update the navigationOptions and add a new button, after my component has updated. Here is the code with which I tried it:
static navigationOptions = ({ navigation }) => {
const options = {
headerTitle: SCREEN_TEXT_MENU_HEADER,
headerStyle: {
borderBottomWidth: 0,
marginBottom: -5
}
};
if (navigation.getParam("drawer", true)) {
options["headerLeft"] = (
<HeaderIconButton
onClick={() => {
navigation.openDrawer();
}}
icon={require("../../assets/icons/burgerMenu.png")}
/>
);
}
if (navigation.getParam("renderBillButton", false)) {
options["headerRight"] = (
<HeaderIconButton
onClick={() => {
navigation.navigate("BillScreen");
}}
type="primary"
icon={require("../../assets/icons/euro.png")}
/>
);
}
return options;
};
componentDidUpdate = prevProps => {
const { navigation, orders } = this.props;
if (prevProps.orders.length !== orders.length) {
navigation.setParams({
renderBillButton: orders.length > 0
});
}
};
The problem with this approach is, that the navigationOptions do not get reset after componentDidUpdate(). How can I dynamically adjust the header with React Navigation?
You can use this.props.navigation.setParams() function to update the navigation state params.
Reference: https://reactnavigation.org/docs/en/headers.html#updating-navigationoptions-with-setparams
Okay here is what went wrong: I also had to call the same code within componentDidMount(), otherwise it would not affect the page upon loading. So in addition to the code of my question I added:
componentDidMount = () => {
const { navigation, order } = this.props;
navigation.setParams({
renderBillButton: orders.length > 0
});
}

React-native-navigation Change state from another tabnavigator

I'm using react-navigation / TabNavigator, is there a way to change the state of a tab from another tab without using Redux or mobx?
Yes you can. It is a little complicated, a little hacky and probably has some side-effects but in theory you can do it. I have created a working example snack here.
In react-navigation you can set parameters for other screens using route's key.
When dispatching SetParams, the router will produce a new state that
has changed the params of a particular route, as identified by the key
params - object - required - New params to be merged into existing route params
key - string - required - Route key that should get the new params
Example
import { NavigationActions } from 'react-navigation'
const setParamsAction = NavigationActions.setParams({
params: { title: 'Hello' },
key: 'screen-123',
})
this.props.navigation.dispatch(setParamsAction)
For this to work you need to know key prop for the screen you want to pass parameter. Now this is the place we get messy. We can combine onNavigationStateChange and screenProps props to get the current stacks keys and then pass them as a property to the screen we are currently in.
Important Note: Because onNavigationStateChange is not fired when the app first launched this.state.keys will be an empty array. Because of that you need to do a initial navigate action.
Example
class App extends Component {
constructor(props) {
super(props);
this.state = {
keys: []
};
}
onNavigationChange = (prevState, currentState) => {
this.setState({
keys: currentState.routes
});
}
render() {
return(
<Navigation
onNavigationStateChange={this.onNavigationChange}
screenProps={{keys: this.state.keys}}
/>
);
}
}
And now we can use keys prop to get the key of the screen we need and then we can pass the required parameter.
class Tab1 extends Component {
onTextPress = () => {
if(this.props.screenProps.keys.length > 0) {
const Tab2Key = this.props.screenProps.keys.find((key) => (key.routeName === 'Tab2')).key;
const setParamsAction = NavigationActions.setParams({
params: { title: 'Some Value From Tab1' },
key: Tab2Key,
});
this.props.navigation.dispatch(setParamsAction);
}
}
render() {
const { params } = this.props.navigation.state;
return(
<View style={styles.container}>
<Text style={styles.paragraph} onPress={this.onTextPress}>{`I'm Tab1 Component`}</Text>
</View>
)
}
}
class Tab2 extends Component {
render() {
const { params } = this.props.navigation.state;
return(
<View style={styles.container}>
<Text style={styles.paragraph}>{`I'm Tab2 Component`}</Text>
<Text style={styles.paragraph}>{ params ? params.title : 'no-params-yet'}</Text>
</View>
)
}
}
Now that you can get new parameter from the navigation, you can use it as is in your screen or you can update your state in componentWillReceiveProps.
componentWillReceiveProps(nextProps) {
const { params } = nextProps.navigation.state;
if(this.props.navigation.state.params && params && this.props.navigation.state.params.title !== params.title) {
this.setState({ myStateTitle: params.title});
}
}
UPDATE
Now react-navigation supports listeners which you can use to detect focus or blur state of screen.
addListener - Subscribe to updates to navigation lifecycle
React Navigation emits events to screen components that subscribe to
them:
willBlur - the screen will be unfocused
willFocus - the screen will focus
didFocus - the screen focused (if there was a transition, the transition completed)
didBlur - the screen unfocused (if there was a transition, the transition completed)
Example from the docs
const didBlurSubscription = this.props.navigation.addListener(
'didBlur',
payload => {
console.debug('didBlur', payload);
}
);
// Remove the listener when you are done
didBlurSubscription.remove();
// Payload
{
action: { type: 'Navigation/COMPLETE_TRANSITION', key: 'StackRouterRoot' },
context: 'id-1518521010538-2:Navigation/COMPLETE_TRANSITION_Root',
lastState: undefined,
state: undefined,
type: 'didBlur',
};
If i understand what you want Its how i figure out to refresh prevous navigation screen. In my example I refresh images witch i took captured from camera:
Screen A
onPressCamera() {
const { navigate } = this.props.navigation;
navigate('CameraScreen', {
refreshImages: function (data) {
this.setState({images: this.state.images.concat(data)});
}.bind(this),
});
}
Screen B
takePicture() {
const {params = {}} = this.props.navigation.state;
this.camera.capture()
.then((data) => {
params.refreshImages([data]);
})
.catch(err => console.error(err));
}