Persisting data between app launches with Expo & React Native - react-native

Here's my App.js, everything else is as standard/simple as I can get it.
import React from 'react';
import { AsyncStorage, Text, View } from 'react-native';
export default class App extends React.Component {
render() {
console.log("Fetching data")
AsyncStorage.getItem('#MySuperStore:key', (value) => {
console.log("Fetched data: ", value)
if(value == null) {
console.log("Writing data!")
AsyncStorage.setItem('#MySuperStore:key', 'data', () => {
console.log("Wrote data!")
})
}
})
return(
<View>
<Text>Hello, ReplIt</Text>
</View>
);
}
}
The fetched value is always null.
I've tried this both locally and on ReplIt. In all cases, the data does not persist across app loads; I always see:
Fetching data
Fetched data: null
Writing data!
Wrote data!
What am I doing wrong? Do I have an incorrect assumption about how Expo interacts with the persistent storage? AFAIK, AsyncStorage is supposed to save stuff to the device; so I can close and re-open the app and have the data persist.

UPD: i just realized your code worked as expected... probably it is replit issue as mentioned in comment.
Avoid any requests and async calls in render method, because it could be called may times depending on how props or state changing. Better put all related code into componentDidMount as it is recommended in documentation. It will be called only once when component mounted.
Not sure why your code dodnt worked for you, callbacks are allowed for AsyncStorage, however wait works just fine for me:
import React from "react";
import { AsyncStorage, Text, View } from "react-native";
export default class App extends React.Component {
constructor() {
super();
this.state = {
storedValue: null
};
}
async componentDidMount() {
let storedValue = await AsyncStorage.getItem("#MySuperStore:key");
console.log("Fetched data: ", storedValue);
if (storedValue == null) {
console.log("Writing data!");
storedValue = await AsyncStorage.setItem("#MySuperStore:key", "data");
}
this.setState({
storedValue
});
}
render() {
const { storedValue } = this.state;
return (
<View>
<Text>Hello, ReplIt</Text>
<Text>This is Stored Value, '{storedValue}'</Text>
</View>
);
}
}

give these a try. AsyncStorage is a Javascript Promise based method.
AsyncStorage.getItem('#MySuperStore:key')
.then(value => console.log(value))
or
value = await AsyncStorage.getItem('#MySuperStore:key');
console.log(value);

Related

how to implement crud axios in react native using react native class component? (API)

can anyone help me,
i am trying to make a simple CRUD using axios react native with class component,
to pass data from GET to Details data I succeeded, but when on the DETAIL page to retrieve only one data I had problems,
when I try console.log(dataTampung) / console.warn(dataTampung), the data array appears, but we want to display it instead of undefined.
import React, { Component } from 'react'
import { Text, View } from 'react-native'
import axios from 'axios'
export class DetailData extends Component {
constructor(props) {
super(props)
this.state = {
dataTampung: []
}
}
componentDidMount (){
this.getData();
}
getData = () => {
axios.get('http:my_local_ip_endpoind/api.php?on=detail&id='+this.props.route.params.id)
.then( response => {
this. setState({
dataTampung:response.data.data.result
})
})
.catch(function (error) {
console.log(error);
});
}
render() {
const { id } = this.props.route.params;
const { dataTampung } = this.state;
console.warn(dataTampung);
return (
<View>
<Text> This ID = {id} </Text>
<Text> Title = {`${dataTampung.title}`} </Text>
</View>
)
}
}
export default DetailData
And this is how it appears:enter image description here
Please help :(
I apologize in advance for my bad English.
You are trying to access the array with a dot notation. First, access the specific index in the array in thing case dataTampung[0] which will give you the object, then you can access the title as dataTampung[0].title.

React native How to execute function every time when i open page

I need to send request every time when i open page. Currently when i access page first time after load the app everything is ok, but if i go to another page and back after that request is not send it again.
You have to add focus listener so when you go back, It will refresh the data like
import * as React from 'react';
import { View } from 'react-native';
function AppScreen({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// The screen is focused
// Call any action and update data
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [navigation]);
return <View />;
}
source : https://reactnavigation.org/docs/function-after-focusing-screen/
Here you go, example for a class based and functional based component to run something on every load of the screen.
import React, { useEffect } from "react";
import {View} from 'react-native'
//Functional Component
const App = () =>
{
useEffect(() =>
{
myAction();
}, [])
return (
<View>
</View>
);
}
//Class based Component
class App extends Component
{
componentDidMount()
{
this.myAction();
}
render()
{
return(
<View>
</View>
)
}
}

Why Is My Redux Reducer Ignoring Await on Async Functions?

In my React-Native app, I had some database code that worked fine. However, I decided that I needed to shoehorn in redux to maintain certain state, especially app settings.
Once I got the redux concepts through my thick skull and implemented it, that same database code started returning promises instead of honoring the "await" statements that were previously in use.
Here is the relevant reducer and database code:
// relevant imports
export default function divisionReducer(state = {programId: 1}, action) {
switch (action.type) {
case GET_DIVISIONS_BY_PROGRAM:
// add result to state
return _.cloneDeep({...state, divisions: divisions });
default:
return state;
}
}
getAllDivisions = async (programId) => {
let db = await openDefault();
const sql = "SELECT * FROM DIVISION WHERE DIVISION_PROGRAM_ID = ?";
let sqlResult = await query(db, sql, [programId]);
await close(db);
// function to convert db snake case to camelcase
result = keysToCamelCase(sqlResult.result);
return result;
}
My question: why is this code not honoring the "await" keywords?
Edit: More Code Added by Request
Below is the divisionAction code:
import { GET_DIVISIONS_BY_PROGRAM } from "./actionTypes";
export const getAllDivisions = (programId) => {
return {
type: GET_DIVISIONS_BY_PROGRAM,
payload: programId
}
}
Below is the DivisionManagementScreen, which calls the getAllDivisions code:
port React, {Component} from "react";
import {View, FlatList, Alert} from "react-native";
import {withNavigation} from "react-navigation";
import {connect} from "react-redux";
import masterStyles, {listPage, bigButtonStyles} from "./../../styles/master";
import {getAllDivisions} from "./../../redux/actions/divisionActions";
import DivisionManagementRow from "./DivisionManagementRow";
class DivisionManagmementScreen extends Component {
constructor(props) {
super(props);
}
async componentDidMount() {
this.props.getAllDivisions(this.props.programId);
console.log("Props after getAllDivisions: " + JSON.stringify(this.props));
}
async componentWillUnmount() {
console.log("Entered componentWillUnount()");
}
_renderItem = ({item}) => (
<DivisionManagementRow divisionId={item.DIVISION_ID} divisionName={item.DIVISION_NAME}
onAddTeam={() => {this._addTeam(item.DIVISION_ID)}}
onEdit={() => {this._editDivision(item.DIVISION_ID)}}
onDelete={() => {this._btnDeleteDivision(item.DIVISION_ID)}}/>
);
render() {
console.log("In render(), props: " + JSON.stringify(this.props));
return (
<View style={masterStyles.component}>
<View style={listPage.listArea}>
<FlatList
data={this.props.divisions}
renderItem={this._renderItem}
keyExtractor={(item) => item.DIVISION_ID.toString() } />
</View>
<View style={listPage.bottomButtonArea}>
<PortableButton defaultLabel="Add Division"
disabled={false}
onPress={() => {this._addDivision()}}
onLongPress={() => {}}
style={bigButtonStyles} />
</View>
</View>
);
}
}
function mapStateToProps(state) {
return {
programId: state.divisionReducer.programId,
divisions: state.divisionReducer.divisions
};
}
export default withNavigation(connect(mapStateToProps, {getAllDivisions})(DivisionManagmementScreen));
Is this enough code to diagnose?
I can't see where you're actually calling your getAllDivisions async function. I can only see you trying to call the getAllDivisions action creator - action creators just emit actions syncronously, by default they can't call functions with side effects.
If you want to trigger side effects, like your DB async function you need to look into a library like redux-thunk. Or more advanced would be redux-saga. If you're new to this stuff, I advise starting with redux-thunk.
Also I think the way you're using the connect() function is wrong. The second argument mapDispatchToProps needs to actually dispatch your actions to the store. So it should look like this:
function mapStateToProps(state) {
return {
programId: state.divisionReducer.programId,
divisions: state.divisionReducer.divisions
};
}
function mapDispatchToProps(dispatch) {
return {
getAllDivisions () {
dispatch(getAllDivisions())
}
};
}
export default withNavigation(
connect(
mapStateToProps, mapDispatchToProps
)(DivisionManagmementScreen)
)
So, instead of writing my database-enabled action creator correctly (starting with " return (dispatch) => { /* blah blah blah */ } ), I was still having it return an object, and having the reducer call the method with the database code.
I finally got the concepts through my thick skull, and got the code working over a weekend.

State change not making it to the child container

In my react native app I'm using redux to handle state transition of a Post object -- the state is changed by couple of child components. The Post object has properties like title, name, description which the user can edit and Save.
In the reducer Im using React.addons.update return new state object.
The main container view has 2 custom child components (wrapped in TabBarNavigator).
One of the child component has few TextInputs which is updating a state.
Using the logger middleware and console.log() I see the new state value in the parent view's render() (via this.props.name) but not in the child view.
I'm trying to figure out why the updated state is not propagated to the child container. Any suggestion is much appreciated.
Im at a point where Im thinking of subscribeing to the redux store manually in the child container but it feels wrong
my code looks like this:
MainView
Reducer
configure store etc
The MainView
const React = require('react-native');
const {
Component,
} = React;
const styles = require('./../Styles');
const MenuView = require('./MenuView');
import Drawer from 'react-native-drawer';
import TabBarNavigator from 'react-native-tabbar-navigator';
import BackButton from '../components/BackButton';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as PostActions from '../actions/Actions';
import {Details} from './Article/Details';
import {ArticleSecondary} from './Article/Secondary';
var update = require('react-addons-update');
import configureStore from '../store/configureStore';
class ArticleMainView extends Component {
constructor(props){
super(props);
//var store = configureStore(props.route.post);
this.state = {
};
}
componentDidMount(){
}
savePost() {
console.log(this.props.post.data);
this.props.navigator.pop();
}
render(){
console.log("ArticleMainView: render(): " + this.props.name);
return(
<TabBarNavigator
ref="navComponent"
navTintColor='#346293'
navBarTintColor='#94c1e8'
tabTintColor='#101820'
tabBarTintColor='#4090db'
onChange={(index)=>console.log(`selected index ${index}`)}>
<TabBarNavigator.Item title='ARTICLE' defaultTab>
<Details ref="articleDetail"
backButtonEvent={ () => {
this.props.navigator.pop();
}}
saveButtonEvent={ () => {
this.savePost();
}}
{...this.props}
/>
</TabBarNavigator.Item>
<TabBarNavigator.Item title='Secondary'>
<ArticleSecondary ref="articleSecondary"
{...this.props}
backButtonEvent={ () => {
this.props.navigator.pop();
}}
saveButtonEvent={ () => {
this.savePost();
}}
/>
</TabBarNavigator.Item>
</TabBarNavigator>
);
}
}
function mapStateToProps(state) {
return {
post: state,
text: state.data.text,
name: state.data.name,
description: state.data.description
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(PostActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ArticleMainView);
The Reducer:
import {Constants} from '../api/Constants';
var update = require('react-addons-update');
export default function postReducer(state, action) {
switch(action.type) {
case Constants.SET_POST_TEXT:
if( state.data.text){
return update(state, {
data: { $merge: {text: action.text }}
});
}else{
return update(state, {
data: { $merge: {text: action.text }}
});
}
break;
case Constants.SET_POST_NAME:
return update(state, {
data: { name: { $set: action.text }}
});
return newO;
break;
case Constants.SET_POST_DESCRIPTION:
return update(state, {
data: { description: { $set: action.text }}
});
break;
default:
return state;
}
}
render scene of the app:
renderScene(route, navigator) {
switch (route.id) {
case "ArticleMainView":
let store = configureStore(route.post);
delete route.post; // TODO: not sure if I should remove this
return (
<Provider store={store}>
<ArticleMainView navigator={navigator} {...route}/>
</Provider>
);
default:
return <LandingView navigator={navigator} route={route}/>
}
}
configureStore:
import { createStore,applyMiddleware,compose } from 'redux'
import postReducer from '../reducers/SocialPostReducer';
import createLogger from 'redux-logger';
const logger = createLogger();
export default function configureStore(initialState){
return createStore(
postReducer,
initialState,
compose(applyMiddleware(logger))
);
}
If anyone stumbles on this question this is how I solved it. In each of the child components I declared a contextTypes object like so
ChildComponentView.contextTypes = {
store: React.PropTypes.object
}
to access the current state in the child component
let {store} = this.context;
store.getState();
I don’t know React Native well but something that threw me off is that you’re effectively creating a store on every render:
case "ArticleMainView":
let store = configureStore(route.post);
delete route.post; // TODO: not sure if I should remove this
return (
<Provider store={store}>
<ArticleMainView navigator={navigator} {...route}/>
</Provider>
);
Store should only be created once per application lifetime. It never makes sense to create it inside render() or renderScene() or similar methods. Please check the official Redux examples to see how the store is typically created.
Another problem is that you don’t show how you update the data, which child component doesn’t get updated, when you expect it to get updated, and so on. This is a lot of code, and it is very hard to help because it is incomplete, and most of it is not relevant to the problem. I would suggest you to remove all the irrelevant code until you can reproduce the problem with a minimal possible complete example. Then you can amend your question to include that example.

React Native AsyncStorage fetches data after rendering

I am using AsyncStorage in ComponentWillMount to get locally stored accessToken, but it is returning the promise after render() function has run. How can I make render() wait until promise is completed? Thank you.
You can't make a component wait to render, as far as I know. What I've done in the app I'm working on is to add a loading screen until that promise from AsyncStorage resolves. See the examples below:
//
// With class component syntax
//
import React from 'react';
import {
AsyncStorage,
View,
Text
} from 'react-native';
class Screen extends React.Component {
state = {
isLoading: true
};
componentDidMount() {
AsyncStorage.getItem('accessToken').then((token) => {
this.setState({
isLoading: false
});
});
},
render() {
if (this.state.isLoading) {
return <View><Text>Loading...</Text></View>;
}
// this is the content you want to show after the promise has resolved
return <View/>;
}
}
//
// With function component syntax and hooks (preferred)
//
import React, { useEffect } from 'react';
import {
AsyncStorage,
View,
Text
} from 'react-native';
const Screen () => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
AsyncStorage.getItem('accessToken').then((token) => {
setIsLoading(false);
});
}, [])
if (isLoading) {
return <View><Text>Loading...</Text></View>;
}
// this is the content you want to show after the promise has resolved
return <View/>;
}
Setting the isLoading property in state will cause a re-render and then you can show the content that relies on the accessToken.
On a side note, I've written a little library called react-native-simple-store that simplifies managing data in AsyncStorage. Hope you find it useful.
Based on react-native doc, you can do something like this:
import React, { Component } from 'react';
import {
View,
} from 'react-native';
let STORAGE_KEY = '#AsyncStorageExample:key';
export default class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
loaded: 'false',
};
}
_setValue = async () => {
try {
await AsyncStorage.setItem(STORAGE_KEY, 'true');
} catch (error) { // log the error
}
};
_loadInitialState = async () => {
try {
let value = await AsyncStorage.getItem(STORAGE_KEY);
if (value === 'true'){
this.setState({loaded: 'true'});
} else {
this.setState({loaded: 'false'});
this._setValue();
}
} catch (error) {
this.setState({loaded: 'false'});
this._setValue();
}
};
componentWillMount() {
this._loadInitialState().done();
}
render() {
if (this.state.loaded === 'false') {
return (
<View><Text>Loading...</Text></View>
);
}
return (
<View><Text>Main Page</Text></View>
);
}
}
you can use react-native-easy-app that is easier to use than async storage.
this library is great that uses async storage to save data asynchronously and uses memory to load and save data instantly synchronously, so we save data async to memory and use in app sync, so this is great.
import { XStorage } from 'react-native-easy-app';
import { AsyncStorage } from 'react-native';
const initCallback = () => {
// From now on, you can write or read the variables in RNStorage synchronously
// equal to [console.log(await AsyncStorage.getItem('isShow'))]
console.log(RNStorage.isShow);
// equal to [ await AsyncStorage.setItem('token',TOKEN1343DN23IDD3PJ2DBF3==') ]
RNStorage.token = 'TOKEN1343DN23IDD3PJ2DBF3==';
// equal to [ await AsyncStorage.setItem('userInfo',JSON.stringify({ name:'rufeng', age:30})) ]
RNStorage.userInfo = {name: 'rufeng', age: 30};
};
XStorage.initStorage(RNStorage, AsyncStorage, initCallback);
React-native is based on Javascript which does not support blocking functions.Also this makes sense as we don't want the UI to get stuck or seem unresponsive.
What you can do is handles this in the render function. i.e Have a loading screen re-render it as you as you get the info from the AsyncStorage