I want the name of the current route or screen in react-navigation which I want to use inside if condition to make some changes.
For react-navigation v5:
import { useRoute } from '#react-navigation/native';
const route = useRoute();
console.log(route.name);
You can catch it as the following code:
this.props.navigation.state.routeName
If you are using nested navigators, you can use this code to get current active screen's state
import { NavigationState } from 'react-navigation';
const getActiveRouteState = function (route: NavigationState): NavigationState {
if (!route.routes || route.routes.length === 0 || route.index >= route.routes.length) {
return route;
}
const childActiveRoute = route.routes[route.index] as NavigationState;
return getActiveRouteState(childActiveRoute);
}
Usage:
const activeRoute = getActiveRouteState(this.props.navigation.state);
I'm using this when I need to get current active screen's state from NavigationDrawer.
This works fine in react-navigation v5.x
this.props.route.name
const routeNameRef = React.createRef();
<NavigationContainer
ref={navigationRef}
onReady={() => routeNameRef.current = navigationRef.current.getCurrentRoute().name}
onStateChange={() => {
const previousRouteName = routeNameRef.current
const currentRouteName = navigationRef.current.getCurrentRoute().name
if (previousRouteName !== currentRouteName) {
// Do something here with it
}
// Save the current route name for later comparision
routeNameRef.current = currentRouteName
}}
>
{/* ... */}
</NavigationContainer>
);
export function getCurrentRouteName(action) {
return routeNameRef;
}
You can import the function getCurrentRouteName and use this to get the current route name and its working in any nested navigators in React Navigation 5.
While using "react-navigation": "^3.0.8" and DrawerNavigator it can be accessed from the this.props object using
this.props.activeItemKey
Preparation
register šNavigationService.js,see the doc detail in Navigating without the navigation prop
<App
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
recursion function
function getCurrentRoute(nav){
if(Array.isArray(nav.routes)&&nav.routes.length>0){
return getCurrentRoute(nav.routes[nav.index])
}else {
return nav.routeName
}
}
get current routeName
getCurrentRoute(NavigationService.getNavigator().state.nav)
In React Navigation v5, I was able to pull the current route name with the below approach:
import { useNavigationState } from '#react-navigation/native'
const routes = useNavigationState(state => state.routes)
const currentRoute = routes[routes.length -1].name
console.log('currentRoute: ',currentRoute)
It is possible to get this from the navigationRef attached to the navigation container. Where navigationRef is a ref.
export const navigationRef = React.createRef()
<NavigationContainer
ref={navigationRef}
>
<Navigator />
</NavigationContainer>
Then use: const currentRouteName = navigationRef.current.getCurrentRoute().name
Alternatively in a functional component you can useRef const navigationRef = React.useRef()
For react-navigation v5, you could use the useNavigationState hook -
import {useNavigationState} from '#react-navigation/native';
const state = useNavigationState(state => state);
const routeName = (state.routeNames[state.index]);
console.log(routeName);
import {getFocusedRouteNameFromRoute,useRoute} from '#react-navigation/native';
//...
const route = useRoute();
const routeName = getFocusedRouteNameFromRoute(route); // Get Nested Route Name
With version 5.x the best way currently is getFocusedRouteNameFromRoute
import { getFocusedRouteNameFromRoute } from '#react-navigation/native';
export default function Stack(route) {
// If the focused route is not found, we need to assume it's the initial screen
// This can happen during if there hasn't been any navigation inside the screen
// In our case, it's "Feed" as that's the first screen inside the navigator
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Feed';
return <> ..... </>
}
import { useNavigation } from '#react-navigation/native';
const App = () => {
const navigation = useNavigation();
const { dangerouslyGetState } = useNavigation();
const { index, routes } = dangerouslyGetState()
console.log(routes[index].name);
return(
<>
</>
)
};
You can use this in hooks as well.
console.log(navigation.dangerouslyGetState());
this.props.navigation.state.routeName works only in react-navigation 4 but react-navigation 5 doesn't support it.
The current route name can be achieved by using redux:
-Navigator component passes route object as a prop to the child component
-The Child component receives props and could find the route name in route.name
-To get updated route name on the screen change you can use focus event listener on navigation
<====== Parent Component where navigation is implemented ======>
import React from "react";
import { createMaterialTopTabNavigator } from "#react-navigation/material-top-
tabs";
import ChildScreen from "../screens/Home/childScreen";
const Tab = createMaterialTopTabNavigator();
const ParentNavigations = () => {
return (
<Tab.Navigator
>
<Tab.Screen name="ChildScreen" component={ChildScreen} />
</Tab.Navigator>
);
};
export default ParentNavigations;
<===== Child component =====>
import React, { useEffect } from "react";
import { View, StyleSheet } from "react-native";
import { useDispatch } from "react-redux";
import ActionTypes from "../../store/actions/ActionsTypes";
const ChildScreen = ({ navigation, route }) => {
const dispatch = useDispatch();
useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
dispatch({ type: ActionTypes.SETROUTE, payload: route.name }); // every time when screen gets focued it will update the route through redux
});
return unsubscribe;
}, [navigation, route]);
return (
<View style={styles.container}>
<Text>Hello</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0C0B0B",
},
});
export default ChildScreen;
If you just want to see if the current screen is focused, you can use navigation.isFocused().
https://reactnavigation.org/docs/navigation-prop/#isfocused
Example:
_backAction() {
const { navigation } = this.props;
if (navigation.isFocused()) {
this.setState({
isLeavingApp: true,
});
}
}
const Home = ({ navigation, route }) => {
// you will see screen key, name and params
console.log("ROUTE", route);
// rest of your code
};
For react native navigation 5.x use :
props.state.routeNames[props.state.index]
In one line with useNavigationState Hook:
const screenName = useNavigationState((state) => state.routes[state.index].name)
If you are using React Navigation v6 you can use this:
import { useRoute } from '#react-navigation/native';
...
const route = useRoute();
console.log('Current Route: ', route.name);
And if you want to get the name of the screen that you are, and you are inside a nested navigator, you can do this:
import { useNavigationState } from '#react-navigation/native';
...
const routes = useNavigationState(state => state.routes);
const currentRouteIndex =
routes?.length && routes[routes.length - 1].state?.index;
const currentRoute =
routes[routes.length - 1].state?.routeNames[currentRouteIndex];
console.log('Current Route: ', currentRoute);
This simple code worked for me. Just add this function to your Util.ts/js file and from your component pass the navigation as the object.
export const getCurrentScreenName = (navigation: any) => {
return navigation.getState().routes[navigation.getState().index].name;
};
This is step by step procedure of what Justin.Mathew has described in his answer.
Create a new file called RootNavigation.js and put the below content inside.
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef(); // we will access all navigation props by importing this in any of the component
Now import the navigationRef from the RootNavigation.js file, and assign NavigationContainer ref to this. After this step navigationRef can function as navigation prop globally.
// App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
handleNavigationRef = (ref) => {
// DON'T DO navigationRef = ref, cause this will give you "navigationRef is
// read only" error.
navigationRef.current = ref;
}
return (
<NavigationContainer ref={handleNavigationRef}>
{/* ... */}
</NavigationContainer>
);
}
USAGE
Now you can import navigationRef in any of the file, even nested ones. And can use this to get the currentRoute and screen details.
//SomeNestedComonent.js
import { navigationRef } from "path/to/RootNavigation.js";
const route = navigationRef.current?.getCurrentRoute(); //current route object
const currentScreen = route.name; // current screen name
I have multiple TabNavigators nested in a BottomTabNavigator. I get the current route of the TabNavigator with:
const pathAndParams = props.navigation.router.getPathAndParamsForState(props.navigation.state) || {}
const activePath = pathAndParams.path
This worked for me (I did it inside my navigation drawer)!
const getCurrentRoute = nav => {
if (Array.isArray(nav.routes) && nav.routes.length > 0) {
return getCurrentRoute(nav.routes[nav.index]);
} else {
return nav.routeName;
}
};
const currentNavigation = getCurrentRoute(this.props.navigation.state);
If you are using reach navigation version 6 you can retrieve screen name by
props.route.name
We have a lot of answer here but it is hard to apply the fix because navigation is NULL.
WHY?
Scenario 1: We are using hooks function like: useRoute, useNavigationState,... but the navigation don't be mounted yet. So it is null and get the Error.
Scenario 2: We are using navigation object in the current screen like HomeScreen
const Home = ({ navigation, route }) => {
console.log("ROUTE", route);
// rest of your code
};
but navigation is NULL in Root app with presence of NavigationContainer
SOLUTION
Make sure to checking navigation is not NULL by using onReady() method of React navigation.
const navigationRef = useRef();
const [routeName, setRouteName] = useState('');
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
const currentRoute = navigationRef.current.getCurrentRoute();
setRouteName(currentRoute.name);
// Do whatever you want with navigation here!.
}}>
...
</NavigationContainer>);
That's it.
this worked for me try this..
const getCurrentRouteName = () => {
let _index = props.state.index;
let _routeName = props.state.routeNames;
return _routeName[_index]
}
For 'wix/react-native-navigation' below is my working solution,
import { Navigation } from 'react-native-navigation';
// Create a variable and set the value from navigation events
let navComponent = null
Navigation.events().registerComponentDidAppearListener(event => navComponent = event)
// navComponent will have the following structure
{"componentId": "Component9", "componentName": "HomeScreen", "componentType": "Component", "passProps": {}}
In my case, I needed to get the bottom nav index as well, this was my method
import {useNavigationState} from '#react-navigation/native';
then
const routes = useNavigationState(state => state.routes);
let place = routes[routes.length - 1];
if (place.name === 'YOUR_BOTTOM_NAV_NAME') {
if (place.state.index === 0) {
//you are in the main screen(BOTTOM_NAV : index 0)
} else {
//else navigate to index 0 screen
navigation.navigate('FirstScreen');
}
} else if (place.name === 'Another_Screen') {
navigation.navigate('navigate_to_the_place_you_want');
} else {
//otherwise come to the first screen
navigation.navigate('FirstScreen');
}
Try this,
const appNavigation = useNavigation();
const currentRoute = appNavigation.getCurrentRoute();
This worked for me. Navigation, and its state received as props were unreliable(at least for drawer navigator at root).
So I went with this one, which seems to be giving the global navigation state.
Had to use the navigation prop being received in drawer for drawer specific functions like closeDrawer or openDrawer.
export function AppDrawer(props) {
// for closeDrawer, openDrawer etc.
const { navigation: drawerNavigation } = props;
// for referencing current route
const appNavigation = useNavigation();
const currentRoute = appNavigation.getCurrentRoute();
// ... rest of the code
}
Reference for both the variable in console -
Related
I am new to react native / ignite bowser. I am making a react native / ignite bowser project.
In my app first there is a welcome screen which appears when the app starts. There is a 'continue' button in the welcome screen. When I click the 'continue' button it should go to the login screen. But it shows this error:
TypeError: undefined is not an object (evaluating 'navigation.navigate')
I am running the app on an actual physical android device.
Can anyone help me? This is my code in github:
https://github.com/boidurja/smartcope_new.git
This is the part of the code where the error occurs:
<Button title="Countinue" containerStyle={{ flex: 1 }} onPress={ ( ) => navigation.navigate('login') }/>
in this file:
app/screens/auth-screens/welcome-screen.tsx
I was told that in this case the issue is that the props property doesn't have the navigation object, coz in new version of the library we have to use react hooks for that check for react-navigation v5 documentation on how to access the navigation prop in react component. I tried it but was not successful.
I see this error
You don't seem to receive the Navigation object properly. If this is the component you use, use the useNavigation function.
useNavigation is a hook which gives access to navigation object.
It's useful when you cannot pass the navigation prop into the
component directly, or don't want to pass it in case of a deeply
nested child.
import { useNavigation } from '#react-navigation/native';
export const AuthScreensWelcomeScreen: FunctionComponent<AuthScreensWelcomeScreenProps> = observer((props) => {
...
const navigation = useNavigation();
...
<Button title="Countinue" containerStyle={{ flex: 1 }} onPress={ ( ) => navigation.navigate('login') }/>
OR
You can get access to the root navigation object through a ref and
pass it to the RootNavigation which we will later use to navigate.
Usage
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
// add other navigation functions that you need and export them
// any js module
import * as RootNavigation from './path/to/RootNavigation.js';
export const AuthScreensWelcomeScreen: FunctionComponent<AuthScreensWelcomeScreenProps> = observer((props) => {
// ...
<Button title="Countinue" containerStyle={{ flex: 1 }} onPress={ ( ) => RootNavigation.navigate('login') }/>
In welcomscreen.tsx, try changing this line:
const {
navigation
} = props;
To this:
const {
navigation
} = this.props;
Alternately, you could leave that snippet out and alter the button snippet like this:
<Button title="Countinue" containerStyle={{ flex: 1 }} onPress={() => this.props.navigation.navigate('login') }
Hope that helps.
Sorry I am new to React Native, and want to know how to change current input value?
As in my case, if I enter a new word directly into input the previous word or the previous value in the value will continue to appear without changing or replacing the new one.
class component:
Keep the value of the input in state of your component which holds this TextInput component.
class ParentComponent extends React.Component {
constructor (props) {
super(props)
this.state = { queryText: '' }
}
handleInputTextChange = (newText) => {
this.setState({ queryText: newText })
}
render () {
return (<View>
<TextInput
onChangeText={this.handleInputTextChange}
value={this.state.queryText}
/>
</View>)
}
}
Notice How I have used onChangeText and handleInputTextChange to handle new values.
Functional Component:
in the functional components, we use hooks. To hold and update text value we use useState
export default () => {
const [text, setText] = useState("");
return <TextView value={text} onChangeText={setText} />;
};
Hello you can use this method :
this.state = {
email: '13119165220',
}
onChangeText={text => this.setState({ email: text })}
In functional components use
export default () => {
const [text,setText] = React.useState("")
return <TextView
value={text}
onChangeText={setText} />
}
TextInput needs value that it is the value that is gonna be shown inside the TextInput.
And to update that value you use onChangeText that is gonna call whatever function you specify every time the text into the TextInput change.
Depending if you are learning React with hooks or without your code will change:
with hooks:
import React,{useState} from 'react'
//others import
function MyTextInput (props){
const [userInput,setUserInput] = useState()
return (
<TextInput
value = {userInput}
onTextChange = {(text) => setUserInput(text)} /> //is always better call another function
) // where you can validate the input
if your using class and coding without hooks, the logic is the same, just change the syntax:
import React,{Component} from 'react'
//other imports
class MyTextInput extends Component{
constructor(props){
super(props)
this.state = {
userInput:''
}
render(){
return (
<TextInput value = {this.state.userInput}
onChangeText = { text => this.setState({userInput:text}) />
)
}
}
Here the links for the docs, where you can find all the props that TextInput receive with explanation: https://reactnative.dev/docs/textinput
I am using stackNavigator for navigating between screens. I am calling two API's in componentDidMount() function in my second activity. When i load it first time, it gets loaded successfully. Then i press back button to go back to first activity. Then, if i am again going to second activity, the APIs are not called and I get render error. I am not able to find any solution for this. Any suggestions would be appreciated.
If anyone coming here in 2019, try this:
import {NavigationEvents} from 'react-navigation';
Add the component to your render:
<NavigationEvents onDidFocus={() => console.log('I am triggered')} />
Now, this onDidFocus event will be triggered every time when the page comes to focus despite coming from goBack() or navigate.
If the upvoted syntax that uses NavigationEvents component is not working, you can try with this:
// no need to import anything more
// define a separate function to get triggered on focus
onFocusFunction = () => {
// do some stuff on every screen focus
}
// add a focus listener onDidMount
async componentDidMount () {
this.focusListener = this.props.navigation.addListener('didFocus', () => {
this.onFocusFunction()
})
}
// and don't forget to remove the listener
componentWillUnmount () {
this.focusListener.remove()
}
The React-navigation documentation explicitly described this case:
Consider a stack navigator with screens A and B. After navigating to
A, its componentDidMount is called. When pushing B, its
componentDidMount is also called, but A remains mounted on the stack
and its componentWillUnmount is therefore not called.
When going back from B to A, componentWillUnmount of B is called, but
componentDidMount of A is not because A remained mounted the whole
time.
Now there is 3 solutions for that:
Subscribing to lifecycle events
...
React Navigation emits events to screen components that subscribe to
them. There are four different events that you can subscribe to:
willFocus, willBlur, didFocus and didBlur. Read more about them in the
API reference.
Many of your use cases may be covered with the withNavigationFocus
higher-order-component, the <NavigationEvents /> component, or the
useFocusState hook.
the withNavigationFocus
higher-order-component
the <NavigationEvents />
component
the useFocusState hook (deprecated)
withNavigationFocus
higher-order-component
import React from 'react';
import { Text } from 'react-native';
import { withNavigationFocus } from 'react-navigation';
class FocusStateLabel extends React.Component {
render() {
return <Text>{this.props.isFocused ? 'Focused' : 'Not focused'}</Text>;
}
}
// withNavigationFocus returns a component that wraps FocusStateLabel and passes
// in the navigation prop
export default withNavigationFocus(FocusStateLabel);
<NavigationEvents /> component
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const MyScreen = () => (
<View>
<NavigationEvents
onWillFocus={payload => console.log('will focus', payload)}
onDidFocus={payload => console.log('did focus', payload)}
onWillBlur={payload => console.log('will blur', payload)}
onDidBlur={payload => console.log('did blur', payload)}
/>
{/*
Your view code
*/}
</View>
);
export default MyScreen;
useFocusState hook
First install library yarn add react-navigation-hooks
import { useNavigation, useNavigationParam, ... } from 'react-navigation-hooks'
function MyScreen() { const focusState = useFocusState(); return <Text>{focusState.isFocused ? 'Focused' : 'Not Focused'}</Text>; }
Here is my personal solution, using onDidFocus() and onWillFocus() to render only when data has been fetched from your API:
import React, { PureComponent } from "react";
import { View } from "react-native";
import { NavigationEvents } from "react-navigation";
class MyScreen extends PureComponent {
state = {
loading: true
};
componentDidMount() {
this._doApiCall();
}
_doApiCall = () => {
myApiCall().then(() => {
/* Do whatever you need */
}).finally(() => this.setState({loading: false}));
};
render() {
return (
<View>
<NavigationEvents
onDidFocus={this._doApiCall}
onWillFocus={() => this.setState({loading: true})}
/>
{!this.state.loading && /*
Your view code
*/}
</View>
);
}
}
export default MyScreen;
Solution for 2020 / React Navigation v5
Inside your screen's ComponentDidMount
componentDidMount() {
this.props.navigation.addListener('focus', () => {
console.log('Screen.js focused')
});
}
https://reactnavigation.org/docs/navigation-events/
Alternatively: Put the addListener method in constructor instead to prevent duplicated calls
React-navigation keeps the component mounted even if you navigate between screens. You can use the component to react to those events :
<NavigationEvents
onDidFocus={() => console.log('hello world')}
/>
More info about this component here.
According to react-navigation docs we can use as below
componentDidMount () {
this.unsubscribe= this.props.navigation.addListener('focus', () => {
//Will execute when screen is focused
})
}
componentWillUnmount () {
this.unsubscribe()
}
Similar to vitosorriso`s answer but should changed didFocus to focus according to docs
You want to perform something after every time navigating to a component using drawernavigator or stacknavigatorĀ ; for this purpose NavigationEvents fits better than componentDidMountĀ .
import {NavigationEvents} from "react-navigation";
<NavigationEvents onDidFocus={()=>alert("Hello, I'm focused!")} />
NoteĀ : If you want to do the task every time after focusing on a component using drawer navigation or stack navigation then using NavigationEvents is better idea. But if you want to do the task once then you need to use componenetDidMountĀ .
//na pagina que vocĆŖ quer voltar
import {NavigationEvents} from 'react-navigation';
async atualizarEstado() {
this.props.navigation.setParams({
number: await AsyncStorage.getItem('count'),
});}
render() {
return (
<View style={styles.container}>
<NavigationEvents onDidFocus={() => this.atualizarEstado()} />
</View>
);
}
I have face this issue, the problem is when you navigate a page, the first time it call constructor, componentWillmount, render componentDidmount,
but in second time when navigate to the same page it only call render, so if you do any API call or something from componentDidmount it would not be called,
and also componentWillunmount never called.
You can use this method, if you are using react-navigation 5.x with class component, it can solve your problem.
for every class component page add this method and call this method once from the constructor
constructor(props) {
super(props);
this.state = {
...
};
...
this.navigationEventListener(); // call the function
}
navigationEventListener = () => { // add this function
let i = 0;
const initialState = this.state;
this.props.navigation.addListener('focus', () => {
if (i > 0) {
this.setState(initialState, () => {
//this.UNSAFE_componentWillMount(); // call componentWillMount
this.componentDidMount(); // call componentDidMount
});
}
});
this.props.navigation.addListener('blur', () => {
this.componentWillUnmount(); //call componentWillUnmount
++i;
});
}
https://reactnavigation.org/docs/navigation-events/
useEffect(() => {
const unsubscribe = props.navigation.addListener('focus', () => {
// do something
// Your apiCall();
});
return unsubscribe;
}, [props.navigation]);
In React, componentDidMount is called only when component is mounted.I think what you are trying to do is call your API on going back in StackNavigator. You can pass a callback function as parameter when you call navigate like this on Parent Screen:
navigate("Screen", {
onNavigateBack: this.handleOnNavigateBack
});
handleOnNavigateBack = () => {//do something};
And on Child Screen
this.props.navigation.state.params.onNavigateBack();
this.props.navigation.goBack();
Fairly new to React native and its concepts. I have been playing with RN for a while to create an application to fetch API data from
http://jsonplaceholder.typicode.com/photos
I have been looking into the documentation of AsyncStorage to implement how i can cache the API data so that upon terminating the application, it doesn't have to deal with fetching the data from web again and again, but wasn't successfully able to implement it.
It will be great if you can provide me help/suggestion based on it. I have included my source code for the 2 important files in my application, along with the a Test.js file with how i was trying to achieve.
import React, {Component} from 'react';
import { FlatList, View, Text, AsyncStorage, ActivityIndicator } from 'react-native';
import axios from 'axios';
import GalleryDetail from './GalleryDetail';
class GalleryList extends Component {
state = { photos: []};
componentDidMount() {
axios.get('http://jsonplaceholder.typicode.com/photos')
.then(response => this.setState({ photos: response.data }))
.catch((error)=> console.warn("fetch Error: ", error));
}
getPhotos = async()=> {
try {
photos = await AsyncStorage.getItem('GalleryPhotos');
}
catch (error) {
console.error(error);
}
}
savePhotos(){
AsyncStorage.setItem('GalleryPhotos', this.state.photos);
console.log('works !');
}
renderPhoto = ({item})=> {
return <GalleryDetail photo={item}/>
}
keyExtractor = (photo, index) => photo.id;
render () {
if(!this.state.photos){
return <ActivityIndicator/>;
}
return (
<FlatList
data = {this.state.photos}
keyExtractor={this.keyExtractor}
renderItem={this.renderPhoto}
/>
);
}
}
export default GalleryList;
and GalleryDetail linked with GalleryList-
import React, {Component} from 'react';
import { Text, View, Image } from 'react-native';
import Card from './Card';
import CardSection from './CardSection';
const GalleryDetail = (props)=> {
return (
<Card>
<CardSection style = {styles.headerContentStyle}>
<Image
style={styles.thumbnailStyle}
source = {{ uri: props.photo.thumbnailUrl}}/>
<Text style= {styles.textStyle}>{props.photo.title} </Text>
</CardSection>
</Card>
);
};
const styles = {
headerContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around'
},
thumbnailStyle: {
height: 60,
width: 60
},
textStyle: {
fontSize: 12,
//textAlign: 'right',
flexDirection: 'row',
justifyContent: 'flex-end',
flex: 1,
flexWrap: 'wrap',
marginLeft: 5,
marginRight: 5,
}
}
export default GalleryDetail;
My method of trying was that-
Upon launching the application, it will first look in asyncStorage, if it finds the data- it fetches from async otherwise going to the web,fetching and storing again for later use.
I tried to implement somewhat like this in a separate file since i dint wanted to breakdown my already running app. The weird broken syntax is
State = {
photos: []
}
componentDidMount() {
// just a variable acting to fetch data from the stored keyvalue pair
check = AsyncStorage.getItem("PhotosKey").then((response) => {
this.setState({"PhotosKey": response});
}).done();
if(check) {
console.log('Data was fetched!!!!!');
check();
}
else {
console.log("Data was not fetched!");
var Data = axios.get('http://jsonplaceholder.typicode.com/photos').
then(response => this.setState({ photos: response.data })).
catch((error)=> console.warn("fetch Error: ", error));
}
}
Thanks in advance!
async componentDidMount() {
const photoStorage = await AsyncStorage.getItem('GalleryPhotos')
if(photoStorage) {
try {
const photoResp = await axios.get('http://jsonplaceholder.typicode.com/photos')
const photoData = await JSON.stringify(photoResp.data)
await AsyncStorage.setItem('GalleryPhotos', photoData);
} catch(e) {
console.warn("fetch Error: ", error)
}
.then(response => this.setState({ photos: response.data }))
}
}
later
getPhotos = async()=> {
try {
photos = JSON.parse(await AsyncStorage.getItem('GalleryPhotos'));
}
catch (error) {
console.error(error);
}
}
The approach from Subramanya is basically all you need to get started, I'm just going to introduce a state management approach with redux-persist where you can definitely appreciate when your app grows.
Redux Persist is performant, easy to implement, and easy to extend.
Let say you have your app hooked up with redux and implemented a fairly organised state tree, redux-persist stores the entire app state with AsyncStorage or any storage engine of your choice.
For instance, let's assume that your API endpoint returns a collection of photos, all you need to do is update the store, and your users can expect their data is safe and saved with redux-persist.
I have not tested all the code below
Let's define the store first,
import { AsyncStorage } from 'react-native';
import { createStore, compose, applyMiddleware, } from "redux";
import { persistStore } from "redux-persist";
import ReduxThunk from "redux-thunk";
import reducers from "../reducers"
const middleWare = [ReduxThunk]
const store = createStore(
reducers,
{},
compose(applyMiddleware(...middleWare))
)
// you can define more parameters, like blacklist or whitelist a reducer
// also, specify storage engine
persistStore(store, { storage: AsyncStorage });
export default store;
At your app's entry point,
import React, { Component } from "react";
import { Provider } from "react-redux";
import Router from "./Router";
import store from './store';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={store}>
<Router /> // navigator
</Provider>
);
}
}
Finally, your API logic.
// action creator
export storePhoto = photos => {
return {
type: 'STORE_PHOTOS',
payload: photos
}
}
// photos reducer
import { REHYDRATE } from 'redux-persist/constants';
export default (state = {}, action) => {
switch (action.type) {
case STORE_PHOTOS:
return { ...state, photos: action.payload }
// this is where `redux-persist` handles caching
case REHYDRATE:
var incoming = action.payload;
if(incoming) return { ...state, ...incoming }
return state;
default:
return state;
}
};
To retrieve data, you will see that redux abstracts away all the excess logics and there is no more setItem, getItem because redux-persist does that automagically for your already.
import { connect } from "react-redux";
import { storePhotos } from "./actions";
class GalleryList extends Component {
async componentDidMount() {
const photos = await axios.get('http://jsonplaceholder.typicode.com/photos');
storePhoto(photos)
}
renderPhoto = ({ item }) => <GalleryDetail photo={item}/>
keyExtractor = (photo, index) => photo.id;
render () {
return (
<FlatList
data = {this.props.photos}
keyExtractor={this.keyExtractor}
renderItem={this.renderPhoto}
/>
);
}
}
// pull data from photos reducer
const mapStateToProps = ({ photos }) => {
return {
photos: photos.photos
}
}
export default connect(mapStateToProps, { storePhotos })(GalleryList);
To summarise,
Install redux-persist in you project.
Import persistStore and autoRehydrate form redux-persist.
Add autoRehydrate to your store.
Pass your store to persistStore.
Listen to the persist/REHYDRATE action on your reducer and populate state accordingly.
Hope my answer helps!
Answer
Caching the data for a specific period of time
const cacheIntervaInHours = 24
const cacheExpiryTime = new Date()
cacheExpiryTime.setHours(cacheExpiryTime.getHours() + cacheIntervalInHours)
const lastRequest = await AsyncStorage.getItem("lastRequest")
if (lastRequest == null || lastRequest > cacheExpiryTime) {
fetch(`${apiUrl}/blogPosts/recent`)
.then(async (response) => {
return await response.json()
})
.then(async (json) => {
if (!json || json.length == 0) {
throw new Error()
}
AsyncStorage.setItem("lastRequest", new Date());
return await AsyncStorage.setItem('blogPosts', JSON.stringify(json))
})
.catch(error => {
console.error(error)
})
}
After converting the app to redux, my react-navigation got some problem. Previously, before integrating with redux, when I press back button (Physical button) react-navigation back to the previous screen. After integrating with redux, the back button will close the app. But, it's still working with goBack() function.
I'm following the guide: https://reactnavigation.org/docs/guides/redux
And read some code from here : https://github.com/react-community/react-navigation/tree/master/examples/ReduxExample
And, this is my Navigator configuration
export const AppNavigator = StackNavigator(
{
Home: { screen: HomeScreen },
ChatDetail: { screen: ChatDetail },
PulsaDetail: { screen: PulsaDetailScreen },
Pulsa: { screen: Pulsa }
},
{
headerMode: 'none',
}
)
class AppWithNavigation extends Component {
render(){
return(
<AppNavigator navigation={ addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})} />
)
}
}
const mapStateToProps = (state) => ({
nav: state.nav
})
export default connect(mapStateToProps)(AppWithNavigation)
EDIT: It's can be done with manual handle & dispatch back action, but it's can't do it automaticlly? just like before using redux?
BackHandler.addEventListener('hardwareBackPress',() => {
this.props.goBack()
return true
})
After post Github issue in react-navigation repository, I got the answer.
Should add manually the back listener on top of screen / component
// App.js
import { BackAndroid } from 'react-native'
// [...]
componentDidMount() {
BackAndroid.addEventListener('backPress', () => {
const { dispatch, nav } = this.props
if (shouldCloseApp(nav)) return false
dispatch({ type: 'Back' })
return true
})
}
componentWillUnmount() {
BackAndroid.removeEventListener('backPress')
}
// [...]
https://github.com/react-community/react-navigation/issues/2117
https://github.com/react-community/react-navigation/issues/117
UPDATE:
https://facebook.github.io/react-native/docs/backhandler