How can I transfer data between screens but not switch screens in react native - react-native

I have a problem how to pass user data after logging in via the 'profile' screen BUT it is not switched to the profile screen?
I only know the code below is the data transfer is complete then the screen will switch? I searched a lot on the internet but there was none or maybe I don't know how to search for this keyword.
this.props.navigation.navigate('Profile', {
data: this.state.data
});

You can use Async Storage to access data after switching navigators.
Also, I'm not sure what's the problem with passing data with navigation.navigate. You can use getParam() to get data on the next screen.
Update
Send data:
this.props.navigation.navigate('Profile', {
nickName: 'MohamadKh75'
});
Get data in Profile screen:
const name = this.props.navigation.getParam('nickName', 'defaultValue'); // name will be 'MohamadKh75'
const age = this.props.navigation.getParam('age', 123); // as we didn't pass 'age', the value is 123
Check here for more information!

If you don't want to switch screens but want to pass data across screens, first, since you don't want to switch screens, you don't need this:
this.props.navigation.navigate('Profile', {
data: this.state.data
});
And instead, you need this:
function_name() {
// some code we will discuss below...
}
Second, since you want the data available across screens or more specifically send the data across screens but not go to that screens, you can use local storage like Async Storage (or SQL lite for big projects). That way, the data is available between screens, but not go to that screens.
For example, in Async Storage, you can store data in local but not switch screens:
async storeData(key, value) {
try {
await AsyncStorage.setItem(key, value);
}
}
async button(key, value) {
await this.storeData(key, value);
// In case you like to switch screens anywhere you want,
// you can uncomment the code below:
// this.props.navigation.navigate('you screens')
}
render() {
return(
<Button onPress={() => this.button("data", this.state.data) } />
);
}
Then, you can retrieve it anywhere you want on your screens, whether you want to switch screens or not:
async getData(key) {
try {
const value = await AsyncStorage.getItem(key);
if (value !== null) {
return value;
}
}
}
async button(key) {
const data = await this.getData(key);
}
render() {
return(
<Button onPress={() => this.button("data") } />
);
}

Related

Issue rendering data from firestore document in react native

I created a map for the array of exercises in my database, and then for each exercise, which is a document reference, I'm getting the data from that document reference and setting it to a state. This is resulting in an infinite loop right now.
If I remove the setExerciseData line, the console logs the exercise object's data that I'm expecting to see. I'm really not sure what the correct way to render the name field from this data is.
{workout.exercises.map((exercise) => {
async function getData(exercise) {
getDoc(exercise).then((doc) => {
console.log(doc.data());
setExerciseData(doc.data());
});
}
getData(exercise);
return (
<Text>{exerciseData.name}</Text>
)
})}
You need to use useEffect() and setState() to be able to render your data. Also, Firebase Firestore is Asynchronous in nature, as a general note, no one should be trying to convert an Async operation into a sync operation as this will cause problems. You need to use an Asynchronous function to fetch data from Firestore. See sample code below:
const getExerciseData = async () => {
const docRef = doc(db, "<collection-name>", '<document-id>')
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
// console.log("Document data:", docSnap.data())
setExerciseData(docSnap.data())
} else {
// doc.data() will be undefined in this case
console.log("No such document!")
}
}
useEffect(() => {
getExerciseData()
}, [])
return (
<Text>{exerciseData.name}</Text>
)
You could also check my answer on this thread for more use-cases.

React Native, how to update async-storage immediately?

I'm making a simple drinking game. When a playing card shows, it's corresponding rule shows below it. I have a settings.js file where the rules are, and the user can see and modify the rules, and they update on the game.js file. I'm using async-storage to store the rules.
I wanted to add a button in the settings.js file, which would return the original rules when pressed. The only problem is, that the original rules don't update immediately on the settings screen. When the button is pressed the original rules do update on the game, but they update on the settings screen only when the user goes back in the game and then back in the settings screen.
The code for updating the rules:
initialState = async () => {
try {
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
...
await AsyncStorage.setItem('rule13', 'theoriginalrule13')
catch(err) {
console.log(err)
}
}
I have the following line of code to update the async-storage when the screen is entered, but as said, it only works when the screen is re-entered:
componentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
this.getData();
});
}
To answer your question here, not in a comment.
Try this :
componentDidMount() {
const { navigation } = this.props;
this.getData();
this.focusListener = navigation.addListener('didFocus', () => {
this.getData();
});
}
I would suggest you to use ,
State driven UI
means your ui will change only when state is changed , now suppose you are changing your asyncStorage, using
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
so I would suggest your state will also update after updating your aysncStorage like.
//Initial state
this.state = { score: 0 };
async storeValues(){
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
let newScoreValue = await AsyncStorage.getItem('rule1')
this.setState({score:newScoreValue})
}
// UI will be like
render(){
return(<Text>{this.state.score}</Text>)
}

How to remount a screen from another screen? (Refresh the whole app again with new parameters)

I have a configurable application which everything is fed into the app from a middleware (like colors and contents) based on a unique id so-called appId.
In the home screen, I am fetching all required data from a middleware in componentDidMount() function and then use it later on. For the first time, I am using a default appId and the componentDidMount() looks like this:
componentDidMount() {
this.setState({ isLoading: true });
fetch(
API +
"configurations" +
"?" +
"uuid=blabla" +
"&" +
"appId=" +
appId +
"&" +
"locale=" +
locale +
"&" +
"gid=" +
gid,
{
method: "GET",
headers: {
Accept: "application/json"
}
}
)}
I have another screen (settings screen) where I have a box and the user can insert appId as input.
When the appId is inserted by the user (in the settings page), I would like to navigate back to the Home screen and re-fetch the data with the new appId that was inserted by the user. The setting screen looks like this:
state = {
newappId: "" };
handlenewappId = text => {
this.setState({ newappId: text });
};
.....
<Item regular>
<Input
onChangeText={this.handlenewappId}
placeholder="Regular Textbox"
/>
<Button
onPress={() => {
navigation.navigate("Home");
}}
>
<Text>Save</Text>
</Button>
</Item>
However, when I do navigation.navigate("Home") the componentDidMount() is not triggered in order to fetch the data again from the middleware (which is expected since it is only triggered for the first time).
What should I do? What is the solution?
I have already tried the solution given in `componentDidMount()` function is not called after navigation
but it didn't work for me.
also tried to move the code in componentDidMount() into a separate function and call it from the settings page but I couldn't make it work.
============== UPDATE: ==============
I was able to solve the issue with the answer given by "vitosorriso" below. However, a new issue occurs. After fetching is done, I am pushing the response to the state and then use it my home screen like this:
fetchData = async () => {
this.setState({ isLoading: true }, async () => {
//fetch the data and push the response to state. e.g:
this.setState({ page: data, configs: data2, isLoading: false });
}}
....
render() {
const { configs, page, isLoading, error } = this.state; //getting the data fetched in the fetch function and pushed to the state
if (isLoading || !page || !configs) {
//if data is not ready yet
);
// Use the data to extract some information
let itemMap = page.item.reduce((acc, item) => {
acc[item.id] = item;
item.attributes = item.attributes.reduce((acc, item) => {
acc[item.key] = item.value;
return acc;
}, {});
return acc;
}, {});
}}
For the first time the app starts, everything works fine and there is no error but if I go to the settings page and press the button to navigate back to the home screen and fetch data again, I face the error:
"items.attributes.reduce is not a function".
I am assuming the reason is, "items.attributes" already has a value (from the first time) and can't fed with new data again.
Is there any way, to clear all the variables when navigating from settings page to the home page?
I have solved the same problem in my app with a similar concept of this ( `componentDidMount()` function is not called after navigation ) but using a different syntax, and it is working for me:
// your home class
// no need to import anything more
// define a separate function to fetch data
fetchData = async () => {
this.setState({ isLoading: true }, async () => {
// fetch your data here, do not forget to set isLoading to false
}
}
// add a focus listener onDidMount
async componentDidMount () {
this.focusListener = this.props.navigation.addListener('didFocus', async () => {
try {
await this.fetchData() // function defined above
} catch (error) {
// handle errors here
}
})
}
// and don't forget to remove the listener
componentWillUnmount () {
this.focusListener.remove()
}

My screen names aren't appearing in Firebase Analytics Dashboard

I am trying to track screen names on react-native-firebase in conjunction with react-navigation.
Here is my code.
const tracker = firebase.analytics()
function getCurrentRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getCurrentRouteName(route);
}
return route.routeName;
}
export default class AppNavigation extends Component {
render() {
StatusBar.setBarStyle('light-content');
return (
<MainScreenNavigator
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = getCurrentRouteName(currentState);
const prevScreen = getCurrentRouteName(prevState);
if (prevScreen !== currentScreen) {
// the line below uses the Google Analytics tracker
// change the tracker here to use other Mobile analytics SDK.
tracker.setCurrentScreen(currentScreen);
}
}}
/>
);
}
}
When I console log the screen names, they appear as desired. However, I'm not seeing the results in Firebase console. When I filter screen by name it just says (not set). Am I doing something wrong in my code? I am importing firebase from 'react-native-firebase' as well.
The code above is solid. It turns out you have to wait a half a day or so before data is populated. Not sure if I missed that in the docs. If you're using react-navigation and firebase, this code works!

How to avoid navigating to other screen multiple times

When press on any button on my React Native App to navigate to a different screen multiple times, then it will redirected to the next screen multiple times.
My sample code is:
// This is my button click event
myMethod()
{
this.props.navigation.navigate("ScreenName")
}
I am using react-navigation to navigate through my app.
How can I fix this behaviour?
I think there are a few ways this could be done. Perhaps recording when the navigation has occurred and preventing it from navigating multiple times.
You may also want to consider resetting hasNavigated after an amount of time etc as well.
// Somewhere outside of the myMethod scope
let hasNavigated = false
// This is my button click event
myMethod()
{
if (!hasNavigated) {
this.props.navigation.navigate("ScreenName")
hasNavigated = true
}
}
This react-navigation issue contains a discussion about this very topic, where two solutions were proposed.
The first, is to use a debouncing function such as Lodash's debounce that would prevent the navigation from happening more than once in a given time.
The second approach, which is the one I used, is to check on a navigation action, whether it is trying to navigate to the same route with the same params, and if so to drop it.
However, the second approach can only be done if you're handling the state of the navigation yourself, for example by using something like Redux.
Also see: Redux integration.
One of solution is custom custom components with adds debounce to onPress:
class DebounceTouchableOpacity extends Component {
constructor(props) {
super(props);
this.debounce = false;
}
_onPress = () => {
if (typeof this.props.onPress !== "function" || this.debounce)
return;
this.debounce = true;
this.props.onPress();
this.timeoutId = setTimeout(() => {
this.debounce = false;
}, 2000);
};
componentWillUnmount() {
this.timeoutId && clearTimeout(this.timeoutId)
}
render() {
const {children, onPress, ...rest} = this.props;
return (
<TouchableOpacity {...rest} onPress={this._onPress}>
{children}
</TouchableOpacity>
);
}
}
another: wrap onPress function into wrapper with similar behavior
const debounceOnPress = (onPress, time) => {
let skipCall = false;
return (...args) => {
if (skipCall) {
return
} else {
skipCall = true;
setTimeout(() => {
skipCall = false;
}, time)
onPress(...args)
}
}
}