React Native Router flux action.key() doesn't work - android

I am building a project on React Native, I am new to React Native. But unfortunately the Actions.key() not working. Here is my code details
App.js
import React, { Component } from 'react';
import { AsyncStorage, ActivityIndicator } from 'react-native';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import { PersistGate } from 'redux-persist/integration/react'
import Spinner from './components/common/Spinner';
import reducers from './reducers';
import Router from './Router';
import { persistStore, persistReducer } from 'redux-persist'
class App extends Component {
render() {
renderLoading = () =>
{
<Spinner size="large" />;
}
const persistConfig = {
key: 'root2',
storage: AsyncStorage
}
const persistedReducer = persistReducer(persistConfig, reducers)
const store = createStore(persistedReducer, {}, applyMiddleware(ReduxThunk));
const persistor = persistStore(store);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Router />
</PersistGate>
</Provider>
);
}
}
export default App;
LoginForm.js
import React, { Component } from 'react';
import { Text } from 'react-native';
import { connect } from 'react-redux';
import { emailChanged, passwordChanged, loginUser, loginUserSuccess1 } from '../actions';
import { Card, CardSection, Input, Button, Spinner } from './common';
import { Actions } from 'react-native-router-flux';
import NameEnter from './NameEnter';
import Router from '../Router';
class LoginForm extends Component {
componentDidMount()
{
console.log (this.props.userDetails);
if(this.props.userDetails != null)
{
if (this.props.userDetails.success !== false) {
console.log (this.props.userDetails.success);
this.props.loginUserSuccess1(this.props.userDetails);
}
}
//Actions.screen1();
}
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
onButtonPress() {
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
renderButton() {
// if (this.props.loading) {
// return <Spinner size="large" />;
// }
return (
<Button onPress={this.onButtonPress.bind(this)}>
Login
</Button>
);
}
render() {
return (
<Card>
<CardSection>
<Input
label="Email"
placeholder="email#gmail.com"
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
</CardSection>
<CardSection>
<Input
secureTextEntry
label="Password"
placeholder="password"
onChangeText={this.onPasswordChange.bind(this)}
value={this.props.password}
/>
</CardSection>
<Text style={styles.errorTextStyle}>
{this.props.error}
</Text>
<CardSection>
{this.renderButton()}
</CardSection>
</Card>
);
}
}
const styles = {
errorTextStyle: {
fontSize: 20,
alignSelf: 'center',
color: 'red'
}
};
const mapStateToProps = ({ auth }) => {
const { email, password, error, loading , userDetails } = auth;
return { email, password, error, loading , userDetails };
};
export default connect(mapStateToProps, {
emailChanged, passwordChanged, loginUser, loginUserSuccess1
})(LoginForm);
AuthActions.js
import { Actions } from 'react-native-router-flux';
import axios from 'react-native-axios';
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGIN_USER,
URL
} from './types';
export const emailChanged = (text) => {
return {
type: EMAIL_CHANGED,
payload: text
};
};
export const passwordChanged = (text) => {
return {
type: PASSWORD_CHANGED,
payload: text
};
};
export const loginUser = ({ email, password }) => {
return (dispatch) => {
dispatch({ type: LOGIN_USER });
axios.post(URL + '/hostLogin', {
email: email,
password: password
})
.then((user) => {
console.log(user.data);
loginUserSuccess(dispatch, user);
})
.catch((error) => {
loginUserFail(dispatch);
});
// firebase.auth().signInWithEmailAndPassword(email, password)
// .then(user => loginUserSuccess(dispatch, user))
// .catch((error) => {
// console.log(error);
// firebase.auth().createUserWithEmailAndPassword(email, password)
// .then(user => loginUserSuccess(dispatch, user))
// .catch(() => loginUserFail(dispatch));
// });
};
};
const loginUserFail = (dispatch) => {
dispatch({ type: LOGIN_USER_FAIL });
};
const loginUserSuccess = (dispatch, user) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: user.data
});
Actions.main();
};
export const loginUserSuccess1 = (user) => {
console.log(1);
return {
type: 'Navigate',
payload: 'Success'
};
};
AuthReducer.js
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGIN_USER
} from '../actions/types';
import { Actions } from 'react-native-router-flux';
const INITIAL_STATE = {
email: '',
password: '',
userDetails: null,
error: '',
loading: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EMAIL_CHANGED:
return { ...state, email: action.payload };
case PASSWORD_CHANGED:
return { ...state, password: action.payload };
case LOGIN_USER:
return { ...state, loading: true, error: '' };
case LOGIN_USER_SUCCESS:
console.log(action.payload );
return { ...state, userDetails: action.payload };
case LOGIN_USER_FAIL:
return { ...state, error: 'Authentication Failed.', password: '', loading: false };
case 'Navigate':
console.log(2);
Actions.main();
default:
return state;
}
};
This is Router.js
import React from 'react';
import { Scene, Router, Actions } from 'react-native-router-flux';
import LoginForm from './components/LoginForm';
import NameEnter from './components/NameEnter';
const RouterComponent = () => {
return (
<Router>
<Scene key="root" hideNavBar>
<Scene key="auth" >
<Scene key="login" component={LoginForm} title="Please Login" />
</Scene>
<Scene key="main">
<Scene key="screen1" component={NameEnter} title="Please Enter Your Name " />
</Scene>
</Scene>
</Router>
);
};
export default RouterComponent;
Now in Authreducer.js i am using Actions.main() to navigate to main . But that is not working . Is something wrong with the settings of router . Please help

It is not working because you have mixed react native commands with redux. What i have noted is that redux always loads first before react native libraries etc loads so it will not work, you have to separate them to make them work.
You can separate them by returning true (also remove Actions.main() from redux file switch statements) and you will read this response from another file(this file will be react native component) where you can use Actions.main() and then it will work.

From the redux documentations:
"Given the same arguments, it [reducers] should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation."
Reducers should always stay pure. It's an antipattern to perform routing transitions inside a reducer.
Beside this: I had the same issue with react native 0.57.0 and react-native-router-flux v4, which is based on React Navigation v2.x. Every method except .key() was working, so I had to downgrade back to rnrf 4.0.0-beta.32. It doesn't seem to be a bug and is more likely caused by a dependency version mismatch (e.g. react & react-native). I will dig deeper into this issue and hopefully find a way to use the newer versions.

Related

How to remove the blank white screen in android phone in react native project?

I am new to react-native.
I am making a project in react-native with my android phone connected to my laptop at the USB port. When I run the project I see a blank screen on my phone. My phone is android version 9.
First I ran npm start and then I ran npm run android.
I ran adb -s device-name reverse tcp:8081 tcp:8081. Still the screen on my phone is blank.
This is my App.tsx file:
import "./i18n"
import React, { useState, useEffect, useRef } from "react"
import { YellowBox } from "react-native"
import { NavigationContainerRef } from "#react-navigation/native";
import { contains } from "ramda"
import { enableScreens } from "react-native-screens"
import { SafeAreaProvider, initialWindowSafeAreaInsets } from "react-native-safe-area-context";
import { RootNavigator, exitRoutes, setRootNavigation } from "./navigation"
import { useBackButtonHandler } from "./navigation/use-back-button-handler"
import { RootStore, RootStoreProvider, setupRootStore } from "./models/root-store"
import * as storage from "./utils/storage"
import getActiveRouteName from "./navigation/get-active-routename"
import { ThemeProvider } from 'react-native-elements';
import * as theme from 'theme';
enableScreens()
YellowBox.ignoreWarnings([
"componentWillMount is deprecated",
"componentWillReceiveProps is deprecated",
"Require cycle:",
])
const canExit = (routeName: string) => contains(routeName, exitRoutes)
export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE"
const App: React.FunctionComponent<{}> = () => {
const navigationRef = useRef<NavigationContainerRef>()
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)
const [initialNavigationState, setInitialNavigationState] = useState()
const [isRestoringNavigationState, setIsRestoringNavigationState] = useState(true)
setRootNavigation(navigationRef)
useBackButtonHandler(navigationRef, canExit)
const routeNameRef = useRef()
const onNavigationStateChange = state => {
const previousRouteName = routeNameRef.current
const currentRouteName = getActiveRouteName(state)
if (previousRouteName !== currentRouteName) {
// track screens.
__DEV__ && console.tron.log(currentRouteName)
}
// Save the current route name for later comparision
routeNameRef.current = currentRouteName
// Clear the storage if we are navigating to auth stack
if ( ['register', 'login', 'forgotpassword'].includes(currentRouteName) ) {
storage.remove(NAVIGATION_PERSISTENCE_KEY);
} else {
// Persist navigation state to storage
storage.save(NAVIGATION_PERSISTENCE_KEY, state)
}
}
useEffect(() => {
(async () => {
setupRootStore().then(setRootStore)
})()
}, [])
useEffect(() => {
const restoreState = async () => {
try {
const state = await storage.load(NAVIGATION_PERSISTENCE_KEY)
if (state) {
setInitialNavigationState(state)
}
} finally {
setIsRestoringNavigationState(false)
}
}
if (isRestoringNavigationState) {
restoreState()
}
}, [isRestoringNavigationState])
if (!rootStore) {
return null
}
return (
<RootStoreProvider value={rootStore}>
<SafeAreaProvider initialSafeAreaInsets={initialWindowSafeAreaInsets}>
<ThemeProvider theme={theme}>
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
</ThemeProvider>
</SafeAreaProvider>
</RootStoreProvider>
)
}
export default App
Can anyone help?

How to remove the blank white screen in my android phone in a react native project?

I am new to react-native.
I am making a project in react-native with my android phone connected to my laptop at the USB port. When I run the project I see a blank screen on my phone. My phone is android version 9.
First I ran npm start and then I ran npm run android.
I ran adb -s device-name reverse tcp:8081 tcp:8081. Still the screen on my phone is blank.
This is my App.tsx file:
import "./i18n"
import React, { useState, useEffect, useRef } from "react"
import { YellowBox } from "react-native"
import { NavigationContainerRef } from "#react-navigation/native";
import { contains } from "ramda"
import { enableScreens } from "react-native-screens"
import { SafeAreaProvider, initialWindowSafeAreaInsets } from "react-native-safe-area-context";
import { RootNavigator, exitRoutes, setRootNavigation } from "./navigation"
import { useBackButtonHandler } from "./navigation/use-back-button-handler"
import { RootStore, RootStoreProvider, setupRootStore } from "./models/root-store"
import * as storage from "./utils/storage"
import getActiveRouteName from "./navigation/get-active-routename"
import { ThemeProvider } from 'react-native-elements';
import * as theme from 'theme';
import { AuthScreensWelcomeScreen } from './screens/auth-screens/welcome-screen';
enableScreens()
YellowBox.ignoreWarnings([
"componentWillMount is deprecated",
"componentWillReceiveProps is deprecated",
"Require cycle:",
])
const canExit = (routeName: string) => contains(routeName, exitRoutes)
export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE"
const App: React.FunctionComponent<{}> = () => {
const navigationRef = useRef<NavigationContainerRef>()
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)
const [initialNavigationState, setInitialNavigationState] = useState()
const [isRestoringNavigationState, setIsRestoringNavigationState] = useState(true)
setRootNavigation(navigationRef)
useBackButtonHandler(navigationRef, canExit)
const routeNameRef = useRef()
const onNavigationStateChange = state => {
const previousRouteName = routeNameRef.current
const currentRouteName = getActiveRouteName(state)
if (previousRouteName !== currentRouteName) {
// track screens.
__DEV__ && console.tron.log(currentRouteName)
}
// Save the current route name for later comparision
routeNameRef.current = currentRouteName
// Clear the storage if we are navigating to auth stack
if ( ['register', 'login', 'forgotpassword'].includes(currentRouteName) ) {
storage.remove(NAVIGATION_PERSISTENCE_KEY);
} else {
// Persist navigation state to storage
storage.save(NAVIGATION_PERSISTENCE_KEY, state)
}
}
useEffect(() => {
(async () => {
setupRootStore().then(setRootStore)
})()
}, [])
useEffect(() => {
const restoreState = async () => {
try {
const state = await storage.load(NAVIGATION_PERSISTENCE_KEY)
if (state) {
setInitialNavigationState(state)
}
} finally {
setIsRestoringNavigationState(false)
}
}
if (isRestoringNavigationState) {
restoreState()
}
}, [isRestoringNavigationState])
if (!rootStore) {
return null
}
return (
<SafeAreaProvider initialSafeAreaInsets={initialWindowSafeAreaInsets}>
<AuthScreensWelcomeScreen />
</SafeAreaProvider>
)
}
export default App
Here is the welcome screen that should appear:
import React, { FunctionComponent, useState } from "react"
import { observer } from "mobx-react-lite"
import { Dimensions, Image } from "react-native"
import { Screen, Button } from "components"
import Carousel, { Pagination } from 'react-native-snap-carousel';
import { Block } from "components/block/block"
const { width: ScreenWidth } = Dimensions.get('screen');
import { NavigationProp, NavigationState } from "#react-navigation/native";
export interface AuthScreensWelcomeScreenProps {
navigation?: NavigationProp<Record<string, object>, string, NavigationState,
{}, {}>
}
export const AuthScreensWelcomeScreen:
FunctionComponent<AuthScreensWelcomeScreenProps> = observer((props) => {
const {
navigation
} = props;
const [activeSlide, setActiveSlide ] = useState(0);
const items = [
{ image: require('../../../assets/splash1.jpg') },
{ image: require('../../../assets/splash2.jpg') },
{ image: require('../../../assets/splash3.jpg') }
];
function renderItem ( { item, index } ) {
return (
<Image style={{ alignSelf: 'center', flex: 1, resizeMode: 'contain', width: ScreenWidth / 1.25 }} source={item.image}></Image>
)
}
return (
<Screen preset="fixed">
<Block flex>
<Carousel
sliderWidth={ScreenWidth}
itemWidth={ScreenWidth}
data={ items }
layout="default"
renderItem={ renderItem }
onSnapToItem={ index => { setActiveSlide( index ) } }/>
<Block>
<Pagination
activeDotIndex={activeSlide}
dotsLength={items.length}
dotStyle={{
width: 10,
height: 10
}}/>
</Block>
</Block>
<Block row my={20} space="evenly" px={20}>
<Button title="Continue" containerStyle={{ flex: 1 }} onPress={ ( ) => navigation.navigate('login') }/>
</Block>
</Screen>
)
})
Can anyone help?
If rootStore does not exist, then your render method returns null, so the page will be blank. Try this:
if (!rootStore) {
console.log('rootStore is not found');
return null
}
console.log('continue to render');
If you do not see continue to render printed out in your Metro server window, then that indicates rootStore was not found. Then you need to figure out why it doesn't exist, or at least show some kind of informative view to handle that case.
I replaced this:
<RootStoreProvider value={rootStore}>
<SafeAreaProvider initialSafeAreaInsets={initialWindowSafeAreaInsets}>
<ThemeProvider theme={theme}>
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
</ThemeProvider>
</SafeAreaProvider>
</RootStoreProvider>
with
<SafeAreaProvider initialSafeAreaInsets={initialWindowSafeAreaInsets}>
<AuthScreensWelcomeScreen />
</SafeAreaProvider>
to get the blank white screen to go away. Please tell me if this is the correct way to do it.

Connect to a device with BLE Android

I'm trying to connect to a device using BLE connection in react-Native on Android Device.
I need to connect to a device with a with a specific name: for example "deviceName".
I'm using react-native-ble-plx.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
ScrollView,
FlatList,
TextInput,
Platform,
Alert
} from 'react-native';
import { BleManager } from 'react-native-ble-plx';
export default class Main extends Component {
constructor(props) {
super(props);
this.state={
scaning:false,
isConnected:false,
text:'',
writeData:'',
receiveData:'',
readData:'',
bleManager: new BleManager(),
data:[],
isMonitoring:false,
}
this.bluetoothReceiveData = [];
this.deviceMap = new Map();
}
scan() {
if(!this.state.scaning) {
this.setState({scaning:true});
this.deviceMap.clear();
const { bleManager } = this.state;
bleManager.startDeviceScan(null, null, async (error, device) => {
console.log("scanning bluetooth...")
if (device.name === "Proximity") {
bleManager.connectToDevice(device.id, {
autoconnect: true,
timeout: BLUETOOTH_TIMEOUT,
isConnected: true
})
// ............
}
})
}
}
disconnect(){
bleManager.disconnect()
.then(res=>{
this.setState({data:[...this.deviceMap.values()],isConnected:false});
})
.catch(err=>{
this.setState({data:[...this.deviceMap.values()],isConnected:false});
})
}
render(){
return(
<View>
<TouchableOpacity
activeOpacity={0.7}
style={[styles.buttonView,{marginHorizontal:10,height:40,alignItems:'center'}]}
onPress={this.state.isConnected?this.disconnect.bind(this):this.scan.bind(this)}>
<Text style={styles.buttonText}>{this.state.scaning?'Search':this.state.isConnected?'Disconnect Bluetooth':'Search Bluetooth'}</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor:'white',
marginTop:Platform.OS == 'ios'?20:0,
},
item:{
flexDirection:'column',
borderColor:'rgb(235,235,235)',
borderStyle:'solid',
borderBottomWidth:StyleSheet.hairlineWidth,
paddingLeft:10,
paddingVertical:8,
},
buttonView:{
height:30,
backgroundColor:'rgb(33, 150, 243)',
paddingHorizontal:10,
borderRadius:5,
justifyContent:"center",
alignItems:'center',
alignItems:'flex-start',
marginTop:10
},
buttonText:{
color:"white",
fontSize:12,
},
content:{
marginTop:5,
marginBottom:15,
},
textInput:{
paddingLeft:5,
paddingRight:5,
backgroundColor:'white',
height:50,
fontSize:16,
flex:1,
},
})
At the moment I receive this error: "undefined is not an object (evaluating 'b.default.startDeviceScan').
How can I fix this error? and do you think the code can work to connect directly to a device? thank you
You are exporting BleManager wrong. You have to put it between braces like this:
import { BleManager } from 'react-native-ble-plx';
You are using BleManager wrong too. You have to instantiate it in some place, I use to use it in the state, to ensure that I have only 1 BleManager and make a new Object of BleManager like this:
constructor {
....
this.state = {
....
bleManager: new BleManager(),
....
};
And then use this.state.bleManager instead of BleManager you was using like this:
const { bleManager } = this.state;
bleManager.startDeviceScan(...)

How to Cache API data using AsyncStorage React Native

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)
})
}

React Native Redux: props not updating after API call

I am quite new to React / React Native / Redux so I feel I am doing something wrong.
The problem
I want to show a spinner while an API is called, and an error message once this API call fails. Props are not updating, and so the components don't show the desired message or spinner
The code (only the relevant chunks)
The component
class Home extends Component {
componentWillMount() {
this.props.tokenGet();
}
renderSpinner() {
if (this.props.loading) {
return (
<Spinner size="large" />
);
}
return null;
}
renderMessage() {
if (this.props.message) {
return (
<Text style={{flex: 1, background: red, color: black}}>
{ this.props.message }
</Text>
)
}
return null;
}
render() {
return (
{ this.renderSpinner() }
{ this.renderMessage() }
)
}
}
const mapStateToProps = (state) => {
const { auth } = state;
const {
loading,
token,
message
} = auth || {
loading: false,
token: null,
message: null
};
return {
loading,
token,
message
}
};
export default connect(mapStateToProps, { tokenGet } )(Home);
The action creator
export const tokenGet = () => {
return (dispatch) => {
dispatch({ type: 'TOKEN_GET_START'});
// Perform the actual API call
let requestToken = fetch(apiBaseUrl + "/tokens", {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(.....)
});
Promise
.race([timeout, requestToken])
.then((response) => response.json())
.then((responseJson) => {
... not relevant ...
})
.catch((error) => {
dispatch({ type: 'TOKEN_GET_FAIL', payload: error});
});
The timeout function, which gets called when the server fails to respond
let timeout = new Promise((resolve, reject) => {
setTimeout(reject, 2000, 'Request timed out. Please check your internet connection.');
});
The reducer
import {
TOKEN_GET_START,
TOKEN_GET_SUCCESS,
TOKEN_GET_FAIL
} from '../actions/types';
const INITIAL_STATE = {
loading: false,
token: null,
message: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case TOKEN_GET_START:
return { ...state, loading: true };
case TOKEN_GET_SUCCESS:
return { ...state, loading: false, token: action.payload };
case TOKEN_GET_FAIL:
return { ...state, loading: false, message: action.payload };
default:
return state;
}
};
The combined reducers
import { combineReducers } from 'redux';
import AuthReducer from './AuthReducer';
export default combineReducers({
auth: AuthReducer
});
The actual behavior is that the props don't change and no message or spinner is visible. With some console logs I know that the API call ends because of the timeout. I am not sure if the state gets updated properly though. I don't know in at which point I can console log this.
It turned out to be because of the quotes in 'TOKEN_GET_FAIL'
That is a string and not the const I need. So I changed to TOKEN_GET_FAIL and it works.

Categories

Resources