I am trying when the user clicks on bottomTabNavigator the component screen will reload. I mean in my first component screen "AnotherA.js", I am using textinput which store user entered data in async storage and in another component "AnotherB.js" I am using get() of async storage to show my stored data on the screen. I am able to see the stored data the first time while reloading the whole app.
I am trying to get data without reloading, the whole app, by navigating with bottomTabNavigator it displays immediately.
//App.js
import React, { Component } from "react";
import { createAppContainer } from 'react-navigation';
import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
import { TabNavigator } from 'react-navigation';
import AnotherA from './AnotherA';
import AnotherB from './AnotherB';
const AppNavigator = createMaterialBottomTabNavigator(
{
AnotherA: { screen: AnotherA },
AnotherB: { screen: AnotherB }
},
{
initialRouteName: 'AnotherA',
activeColor: '#f0edf6',
inactiveColor: '#3e2465',
barStyle: { backgroundColor: '#694fad' },
pressColor: 'pink',
},
{
//tabBarComponent: createMaterialBottomTabNavigator /* or TabBarTop */,
tabBarPosition: 'bottom',
defaultnavigationOptions: ({ navigation }) => ({
tabBarOnPress: (scene, jumpToIndex) => {
console.log('onPress:', scene.route);
jumpToIndex(scene.index);
},
}),
}
);
const AppContainer = createAppContainer(AppNavigator);
export default AppContainer;
//AnotherA.js
import React, { Component } from 'react';
import { AppRegistry, AsyncStorage, View, Text, Button, TextInput, StyleSheet, Image, TouchableHighlight, Linking } from 'react-native';
import styles from './styles';
export default class AnotherA extends Component {
constructor(props) {
super(props);
this.state = {
myKey: '',
text1: '',
}
}
async getKey() {
try {
//const value = await AsyncStorage.getItem('#MySuperStore:key');
const key = await AsyncStorage.getItem('#MySuperStore:key');
this.setState({
myKey: key,
});
} catch (error) {
console.log("Error retrieving data" + error);
}
}
async saveKey(text1) {
try {
await AsyncStorage.setItem('#MySuperStore:key', text1);
} catch (error) {
console.log("Error saving data" + error);
}
}
async resetKey() {
try {
await AsyncStorage.removeItem('#MySuperStore:key');
const value = await AsyncStorage.getItem('#MySuperStore:key');
this.setState({
myKey: value,
});
} catch (error) {
console.log("Error resetting data" + error);
}
}
componentDidMount() {
this.getKey();
}
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Enter Data"
value={this.state.myKey}
onChangeText={(value) => this.setState({ text1: value })}
multiline={true}
/>
<Button
onPress={() => this.saveKey(this.state.text1)}
title="Save"
/>
<Button
//style={styles.formButton}
onPress={this.resetKey.bind(this)}
title="Reset"
color="#f44336"
accessibilityLabel="Reset"
/>
</View>
)
}
}
//AnotherB.js
import React, { Component } from 'react';
import { AppRegistry, AsyncStorage, View, Text, Button, TextInput, StyleSheet, Image, TouchableHighlight, Linking } from 'react-native';
import styles from './styles';
export default class AnotherB extends Component {
constructor(props) {
super(props);
this.state = {
myKey: '',
text1: '',
}
}
async getKey() {
try {
//const value = await AsyncStorage.getItem('#MySuperStore:key');
const key = await AsyncStorage.getItem('#MySuperStore:key');
this.setState({
myKey: key,
});
} catch (error) {
console.log("Error retrieving data" + error);
}
}
componentDidMount() {
this.getKey();
}
render() {
//const { navigate } = this.props.navigation;
//const { newValue, height } = this.state;
return (
<View style={styles.container}>
<Text>{this.state.myKey}</Text>
</View>
)
}
}
Please suggest, I am new to React-Native.
The issue is that you are retrieving the value from AsyncStorage when the component mounts. Unfortunately that isn't going to load the value on the screen when you switch tabs. What you need to do is subscribe to updates to navigation lifecycle.
It is fairly straight forward to do. There are four lifecycle events that you can subscribe to. You can choose which of them that you want to subscribe to.
willFocus - the screen will focus
didFocus - the screen focused (if there was a transition, the transition completed)
willBlur - the screen will be unfocused
didBlur - the screen unfocused (if there was a transition, the transition completed)
You subscribe to the events when the component mounts and then unsubscribe from them when it unmounts. So when the event you have subscribed to happens, it will call the function that you have put into the subscriber's callback.
So you can do something like this in you AnotherB.js:
componentDidMount() {
// subscribe to the event that we want, in this case 'willFocus'
// when the screen is about to focus it will call this.getKey
this.willFocusSubscription = this.props.navigation.addListener('willFocus', this.getKey);
}
componentWillUnmount() {
// unsubscribe to the event
this.willFocusSubscription.remove();
}
getKey = async () => { // update this to an arrow function so that we can still access this, otherwise we'll get an error trying to setState.
try {
const key = await AsyncStorage.getItem('#MySuperStore:key');
this.setState({
myKey: key,
});
} catch (error) {
console.log("Error retrieving data" + error);
}
}
Here is a quick snack that I created showing it working, https://snack.expo.io/#andypandy/navigation-life-cycle-with-asyncstorage
You can try to add then after getItem
AsyncStorage.getItem("#MySuperStore:key").then((value) => {
this.setState({
myKey: value,
});
})
.then(res => {
//do something else
});
Related
I want to Set a Value and Get it in Render with one line code, by adding a Variable between tags. This could shows error :(Can't find variable: storage_key)
import React, { Component } from 'react'
import { View, Text } from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
class SyncRu extends Component {
state = {
'storage_Key': ''
}
render() {
const storeData = async (value) => {
try {
await AsyncStorage.setItem('#storage_Key', value)
} catch (e) {
// saving error
}
}
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#storage_Key')
if (value !== null) {
// value previously stored
}
} catch (e) {
// error reading value
}
}
return (
<View>
<Text>
{storage_Key}
</Text>
</View>
)
}
}
export default SyncRu
You have initialized the state incorrectly. Instead of:
state = {
'storage_Key': ''
}
You have to use:
this.state = {storage_Key: ""};
After you access your data from AsyncStorage you have to use setState, for updating your UI.
const value = await AsyncStorage.getItem('#storage_Key')
if (value !== null) {
this.setState({
storage_Key: value
});
}
Also have a look at the documentation of React State.
React State Documentation
I am making auth component using react-native.
And the code below sends to 'MainTab' of 'this.props.navigation' depends on the result of axios.
<TouchableOpacity onPress={this.handleSubmit}>
<Text>Save</Text>
</TouchableOpacity>
handleSubmit = () => {
const result = await axios.post(
'http://192.0.0.1:4000/clients',
users
);
if (result.data.success) {
return this.props.navigation.navigate('MainTab');
}
return false
};
But I want to use handleSubmit at an other 'js' file to avoid doing repeatedly.
Thus, I edit a code like below.
import { saveSettings } from '../../storage/settingsStorage'
handleSubmit(): void {
saveSettings(this.state);
}
// in 'settingsStorage.js'
export const saveSettings = async users => {
try {
const result = await axios.post(
'http://192.0.0.1:4000/clients/token',
users
);
if (result.data.success) {
return this.props.navigation.navigate('MainTab');
}
return false
} catch (e) {
console.log(e);
}
};
And in this case, I know 'this.props' can't be passed in normal Js file without passing props. But I don't know how can I pass the props?
Thank you so much for reading this.
Based on your description I think you can just add a second parameter to saveSettings and pass the navigation object through:
import { saveSettings } from '../../storage/settingsStorage'
handleSubmit(): void {
saveSettings(this.state, this.props.navigation);
}
// in 'settingsStorage.js'
export const saveSettings = async (users, navigation) => {
try {
const result = await axios.post(
'http://192.0.0.1:4000/clients/token',
users
);
if (result.data.success) {
return navigation.navigate('MainTab');
}
return false
} catch (e) {
console.log(e);
}
};
Instead of passing navigation prop you can use a technique Navigating without the navigation prop as described in official site .
App.js
import { createStackNavigator, createAppContainer } from 'react-navigation';
import NavigationService from './NavigationService';
const TopLevelNavigator = createStackNavigator({
/* ... */
});
const AppContainer = createAppContainer(TopLevelNavigator);
export default class App extends React.Component {
// ...
render() {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
we define NavigationService which is a simple module with functions that dispatch user-defined navigation actions.
/ NavigationService.js
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
// add other navigation functions that you need and export them
export default {
navigate,
setTopLevelNavigator,
};
Now use it every where without navigation prop
// any js module
import NavigationService from 'path-to-NavigationService.js';
// ...
NavigationService.navigate('MainTab');
I have React Native app with MobX store. And i use useEffect hook to call fetch action from MobX to get data from API. The rendering is pretty strange. It looks like this:
useEffect call MobX action with fetch -> loading data, but can not render, the loading is not stopping -> push the button and change the navigation stack -> the data is appearing on a previous screen where before it could not rendered -> come back to the previous screen and see the data that before could not came.
It means only when the navigation stack is changing the data is rendering. It looks like a problem with change MobX state. Can you help me please.
MobX state:
import { createContext } from 'react'
import { action, decorate, observable, computed, runInAction } from 'mobx'
import fetchData from '../utils/fetchData'
import mapObjects from '../utils/mapObjects'
class DataStore {
data = null
error = false
loading = true
get getData(){
return this.data
}
get getError(){
return this.error
}
get getLoading(){
return this.loading
}
async fetchData(url) {
this.data = null
this.error = false
this.loading = true
try {
console.log('TRY')
const response = await fetch(url)
const jsonResponse = await response.json()
const obj = await mapObjects(jsonResponse)
runInAction(() => {
console.log('WRITE!!!')
this.loading = false
this.data = obj
})
} catch (err) {
runInAction(() => {
console.log(err)
this.loading = false
this.error = err
})
}
}
}
decorate(DataStore, {
data: observable,
error: observable,
loading: observable,
fetchData: action
})
export default createContext(new DataStore())
Render component:
import React, { useContext, useEffect, useState } from 'react'
import { ActivityIndicator, FlatList, Platform, StyleSheet, View } from 'react-native'
import DataStore from '../mobx/DataStore'
import { autorun } from 'mobx'
import { ChartsHeader, CryptoItem, IconsHeader, ProjectStatusBar } from '../components'
import { useFetch } from '../hooks/useFetch'
import { WP, HP } from '../constants'
const styles = StyleSheet.create({
container: {
flex: 1
}
})
const ChartsScreen = ({ navigation }) => {
const { container } = styles
const store = useContext(DataStore)
const url = 'https://poloniex.com/public?command=returnTicker'
console.log('store', store)
useEffect(() => {
store.fetchData(url)
}, [])
//*Call custom hook and data distruction
//const { data, error, loading } = useFetch(url)
//*Change percent amount color depends on the amount
const percentColorHandler = number => {
return number >= 0 ? true : false
}
return (
<View style={container}>
{Platform.OS === 'ios' && <ProjectStatusBar />}
<IconsHeader
dataError={store.error}
header="Charts"
leftIconName="ios-arrow-back"
leftIconPress={() => navigation.navigate('Welcome')}
/>
<ChartsHeader />
<ActivityIndicator animating={store.loading} color="#068485" style={{ top: HP('30%') }} size="small" />
<FlatList
data={store.data}
keyExtractor={item => item.key}
renderItem={({ item }) => (
<CryptoItem
name={item.key}
highBid={item.highestBid}
lastBid={item.last}
percent={item.percentChange}
percentColor={percentColorHandler(item.percentChange)}
/>
)}
/>
</View>
)
}
export { ChartsScreen }
In my case it was, because i put all fetch functions to the one hook and call it in useEffect. In the end i have found the decision. I changes my function component to the class component and split all fetch functions in MobX store. Maybe it will be helpful for somebody:
MobX store:
import { action, observable, runInAction } from 'mobx'
class DataStore {
#observable data = null
#observable error = false
#observable fetchInterval = null
#observable loading = false
//*Make request to API
#action.bound
fetchInitData() {
const response = fetch('https://poloniex.com/public?command=returnTicker')
return response
}
//*Parse data from API
#action.bound
jsonData(data) {
const res = data.json()
return res
}
//*Get objects key and push it to every object
#action.bound
mapObjects(obj) {
const res = Object.keys(obj).map(key => {
let newData = obj[key]
newData.key = key
return newData
})
return res
}
//*Main bound function that wrap all fetch flow function
#action.bound
async fetchData() {
try {
runInAction(() => {
this.error = false
this.loading = true
})
const response = await this.fetchInitData()
const json = await this.jsonData(response)
const map = await this.mapObjects(json)
const run = await runInAction(() => {
this.loading = false
this.data = map
})
} catch (err) {
console.log(err)
runInAction(() => {
this.loading = false
this.error = err
})
}
}
//*Call reset of MobX state
#action.bound
resetState() {
runInAction(() => {
this.data = null
this.fetchInterval = null
this.error = false
this.loading = true
})
}
//*Call main fetch function with repeat every 5 seconds
//*when the component is mounting
#action.bound
initInterval() {
if (!this.fetchInterval) {
this.fetchData()
this.fetchInterval = setInterval(() => this.fetchData(), 5000)
}
}
//*Call reset time interval & state
//*when the component is unmounting
#action.bound
resetInterval() {
if (this.fetchInterval) {
clearTimeout(this.fetchInterval)
this.resetState()
}
}
}
const store = new DataStore()
export default store
I am new to React Native, and what i am trying to do is, that i would like to pass getUPCfromApi(upc) to ScanditSDK.js component, inside the onScan function. But i get this error see attached img.
Screenshot of error message
Api.js
import React, { Component } from 'react';
import ScanditSDK from './components/ScanditSDK'
export default class Api extends Component{
getUPCfromApi = (upc) => {
try {
let response = fetch(
`https://api.upcdatabase.org/product/${upc}/API_KEY`);
let responseJson = response.json();
return responseJson;
console.log('response',responseJson);
} catch (error) {
console.error(error);
}
}
render(){
return(
<ScanditSDK
getUPCfromApi={this.getUPCfromApi}
/>
)
}
}
ScanditSDK.js contains scanner module. Inside the onScan method i am calling getUpcFromApi(upc) which i made inside Api.js
ScanditSDK.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
findNodeHandle,
View
} from 'react-native';
import {
BarcodePicker,
ScanditModule,
ScanSession,
Barcode,
SymbologySettings,
ScanSettings
} from 'react-native-scandit';
ScanditModule.setAppKey('APIKEY');
export class ScanditSDK extends Component {
constructor(props){
super(props)
this.state = {
upc: '',
}
}
componentDidMount() {
this.scanner.startScanning();
}
render() {
return (
<View style={{
flex: 1,
flexDirection: 'column'}}>
<BarcodePicker
onScan={(session) => { this.onScan(session) }}
scanSettings= { this.settings }
ref={(scan) => { this.scanner = scan }}
style={{ flex: 1 }}/>
</View>
);
}
onScan = (session) => {
this.setState({upc:session.newlyRecognizedCodes[0].data })
alert(session.newlyRecognizedCodes[0].data + " " + session.newlyRecognizedCodes[0].symbology);
this.props.getUPCfromApi(this.state.upc)
}
}
You should use props to access your function. change your onScan function as below
onScan = (session) => {
this.setState({upc:session.newlyRecognizedCodes[0].data })
alert(session.newlyRecognizedCodes[0].data + " " + session.newlyRecognizedCodes[0].symbology);
this.props.getUPCfromApi(this.state.upc)
}
And in your Api component. Bind your function in constructor.
constructor(props){
super(props)
this.getUPCfromApi = this.getUPCfromApi.bind(this)
}
When this component loads, the startSync function starts. However, I also have an exit function that navigates to a login screen if pressed during any of this.
What happens though is, if I press the exit button, it brings me back to the login page. However, the async function is still running so when it finishes it will then navigate me to the Identification screen.
I was wondering if there is a way to cancel all Async function's so that nothing is running in the background after the exit button is pushed.
import React, { Component } from 'react';
import { ActivityIndicator, AsyncStorage, Button, StatusBar, Text, StyleSheet, View, } from 'react-native';
import * as pouchDB_helper from '../utils/pouchdb';
type Props = {};
export default class SyncScreen extends Component<Props> {
startSync = async () => {
pouchDB_helper.sync().then((response) => {
AsyncStorage.setItem('initial_sync', 'true');
//navigate to the identification page
this.props.navigation.navigate('Identification');
}, (error) => { Alert.alert("Error", "Syncing failed. Please try again."); });
}
exit = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Login');
}
componentDidMount() {
this.startSync();
}
static navigationOptions = {
title: 'Syncing Settings',
};
render() {
return (
<View style={styles.container}>
<Text style={styles.footerText} onPress={this.exit}>Exit</Text>
</View>
);
}
}