I have a variable in state called isLoading. The idea is to display a loading message while the program is communicating the server, then display the data. However, at ling 24, I get an error:
TypeError: This.setState is not a function (in 'this.setState({ isloadin: false});
import React from "react";
import { StyleSheet, Text, View, AsyncStorage } from "react-native";
var text;
export default class App extends React.Component {
constructor(props) {
super(props);
state = {
isLoading: true
};
}
componentDidMount = () => {
AsyncStorage.getItem("accessToken").then(token => {
postdata(
"http://1.0.0.0:1337/loadTransactions",
{ UserID: 69 },
function(result) {
text = toString(result.Data[1].ID);
text = result.Data[1].Label;
console.log(result.Data[1].Label);
this.setState({
isLoading: false
});
}
);
});
};
render() {
console.log(this.setState.isLoading);
if (this.setState.isLoading) {
console.log(this.setState.isLoading);
return (
<View style={styles.container}>
<Text>Loading....</Text>
</View>
);
} else {
return (
<View style={styles.container}>
<Text>Hi, {text}</Text>
<Text>Test</Text>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
}
});
To maintain the context of a function as the same context where the function was lexically defined, you have to use an arrow function:
componentDidMount = () => {
AsyncStorage.getItem("accessToken").then(token => {
postdata(
"http://204.48.23.161:1337/loadTransactions",
{ UserID: 69 },
function(result) {
// ^^^^^^^ use `result => ` here
text = toString(result.Data[1].ID);
text = result.Data[1].Label;
console.log(result.Data[1].Label);
this.setState({
isLoading: false
});
}
);
});
};
this (ref to the instance of class) might not be available inside the context of AsyncStorage. Save this as another variable and use inside:
componentDidMount = () => {
const self = this;
AsyncStorage.getItem("accessToken").then(token => {
postdata(
"http://204.48.23.161:1337/loadTransactions",
{ UserID: 69 },
function(result) {
text = toString(result.Data[1].ID);
text = result.Data[1].Label;
console.log(result.Data[1].Label);
self.setState({
isLoading: false
});
}
);
});
};
Related
I have trouble trying to retrieve data from AsyncStorage, I can't directly assign a state like that, since it always returns undifined, how can I avoid that?
export default class ListTodo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
};
}
componentDidMount() {
//promise
GetDataAsyncStorage('#TODOS').then((data) => {
this.setState({
data: data,
});
});
}
render() {
const {data} = this.state;
console.log(data); // undifined
return (
<>
<Header />
<View>
<FlatList
data={data}
renderItem={({item}) => <TodoItemComponent data={item} />}
keyExtractor={(item) => item.id}
/>
</View>
</>
);
}
}
Here is my function to get data from asynStorage
export const GetDataAsyncStorage = async (key) => {
try {
let data = await AsyncStorage.getItem(key);
return {status: true, data: JSON.parse(data)};
} catch (error) {
return {status: false};
}
};
Add a state variable isLoading and toggle it after the data is got from AsyncStorage
snack: https://snack.expo.io/#ashwith00/async
code:
export default class ListTodo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
isLoading: false,
};
}
componentDidMount() {
this.getData();
}
getData = () => {
this.setState({
isLoading: true,
});
//promise
GetDataAsyncStorage('#TODOS').then((data) => {
this.setState({
data: data,
isLoading: false,
});
});
};
render() {
const { data, isLoading } = this.state;
return (
<View style={styles.container}>
{isLoading ? (
<ActivityIndicator />
) : data.data ? (
<FlatList
data={data}
renderItem={({ item }) => <Text>{item}</Text>}
keyExtractor={(item, i) => i.toString()}
/>
) : (
<Text>No Data Available</Text>
)}
</View>
);
}
}
Because AsyncStorage itself is asynchronous read and write, waiting is almost necessary, of course, another way to achieve, for example, to create a memory object, bind the memory object and AsyncStorage, so that you can read AsyncStorage synchronously.
For example, using the following development library can assist you to easily achieve synchronous reading of AsyncStorage react-native-easy-app
import { XStorage } from 'react-native-easy-app';
import { AsyncStorage } from 'react-native';
// or import AsyncStorage from '#react-native-community/async-storage';
export const RNStorage = {
token: undefined,
isShow: undefined,
userInfo: undefined
};
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);
I am using a stack Navigator, my main screen is tracker.js, and a second screen macros.js
On macros.js I can add nutritional macros (calories, Fats, Carbs, Protein) manually, and add it to my UsedDailyCalories. However when I go back to my tracker.js, which is automatically got 0 calories and I return to macros.js, the value goes back down to 0. And I am not sure why async storage is not working. Here is my code:
import React from "react";
import {
StyleSheet,
View,
Button,
Text,
TextInput,
Modal,
Animated,
} from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import AsyncStorage from "#react-native-community/async-storage";
import {useDispatch} from "react-redux";
export default class Macros extends React.Component {
static navigationOptions = {
title: "My Macros",
};
constructor(props) {
super(props);
this.getData();
this.state = {
isLoading: true,
dataSource: null,
totalCalsSet: 0,
showModal: false,
showModal2: false,
UsedDailyCalories: 0,
UsedDailyFat: +this.props.navigation.getParam("totalFat", "nothing sent"),
UsedDailyCarbs: 0,
UsedDailyProtein: 0,
CalsFatInput: 0,
CalsProteinInput: 0,
CalsCarbsInput: 0,
CaloriePercentage: 0,
};
let calsTakenFromTracker = this.props.navigation.getParam("totalCal", "nothing sent");
this.state.UsedDailyCalories += calsTakenFromTracker;
}
setMacroGoalModal = () => {
this.setState({
showModal: true,
});
};
AddMacrosModal = () => {
this.setState({
showModal2: true,
});
};
addMacrosManually = (ProteinInput, FatInput, CarbsInput) => {
let CalsProteinInput = ProteinInput * 4;
let CalsFatInput = FatInput * 9;
let CalsCarbsInput = CarbsInput * 4;
let CalsCalorieInput = CalsCarbsInput + CalsFatInput + CalsProteinInput;
let withAddedCalories = this.state.UsedDailyCalories + CalsCalorieInput;
this.setState({
UsedDailyCalories :withAddedCalories,
UsedDailyFat: +FatInput,
UsedDailyCarbs: +CarbsInput,
UsedDailyProtein: +ProteinInput,
showModal2: false,
});
console.log(this.state.UsedDailyCalories);
const firstPair = ["UsedTotalCalories", JSON.stringify(this.state.UsedDailyCalories)];
const secondPair = ["UsedTotalCarbs", JSON.stringify(this.state.UsedDailyCarbs)];
const thirdPair = ["UsedTotalProtein", JSON.stringify(this.state.UsedDailyProtein)];
const fourthPair = ["UsedTotalFat", JSON.stringify(this.state.UsedDailyFat)];
try {
this.setState({});
var usedValues = [firstPair, secondPair, thirdPair, fourthPair];
AsyncStorage.setItem("DATA_KEY", JSON.stringify(usedValues))
} catch (error) {
console.log(error);
}
};
setMacros = async (ProteinInput, FatInput, CarbsInput) => {
let CalsProteinInput = ProteinInput * 4;
let CalsFatInput = FatInput * 9;
let CalsCarbsInput = CarbsInput * 4;
let totalCalsSet = CalsCarbsInput + CalsFatInput + CalsProteinInput;
let CaloriePercentage = (totalCalsSet / 2400) * 100;
this.setState({
totalCalsSet: totalCalsSet,
CalsProteinInput: ProteinInput,
CalsFatInput: FatInput,
CalsCarbsInput: CalsCarbsInput,
showModal: false,
CaloriePercentage: CaloriePercentage,
});
console.log(totalCalsSet);
const firstPair = ["totalCalsSet", JSON.stringify(this.state.totalCalories)];
const secondPair = ["totalCarbsSet", JSON.stringify(CarbsInput)];
const thirdPair = ["totalProteinSet", JSON.stringify(ProteinInput)];
const fourthPair = ["totalFatSet", JSON.stringify(FatInput)];
try {
this.setState({});
var setValues = [firstPair, secondPair, thirdPair, fourthPair];
AsyncStorage.setItem("DATA_KEY", JSON.stringify(setValues))
} catch (error) {
console.log(error);
}
};
getData = async () => {
try {
AsyncStorage.multiGet(["key1", "key2"]).then(response => {
})
} catch(e) {
// read error
}
};
render() {
const { navigate } = this.props.navigation;
let CaloriePercentage = this.state.CaloriePercentage + "%";
return (
//styling for navigation container
<View style={styles.container}>
<View style={styles.topStyle}>
<Text>{this.state.UsedDailyCalories} </Text>
<Text>{this.state.UsedDailyCarbs} </Text>
<View style={styles.setMacros}>
<TouchableOpacity onPress={() => this.setMacroGoalModal()}>
<Text> Set Daily Macro Goal </Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity style={styles.setMacros} onPress={() => this.AddMacrosModal()}>
<Text> add Daily Macro Goal </Text>
</TouchableOpacity>
</View>
<View style={styles.viewOfMacros}>
<Text>Cals: {this.state.totalCalsSet}</Text>
<Text>{Math.floor(this.state.CaloriePercentage)}%</Text>
<View style={styles.progressBar}>
<Animated.View
style={
([StyleSheet.absoluteFill],
{ backgroundColor: "#8BED4F", width: CaloriePercentage })
}
/>
</View>
<Text>Fat: {this.state.CalsFatInput}</Text>
<Text>Carbs: {this.state.CalsCarbsInput}</Text>
<Text>Protein: {this.state.CalsProteinInput}</Text>
</View>
<View>
setState is asynchronous, so if you call setState and then immediately try to access its values they will be old.
You can either use the callback available as the second argument to setState, check that out here or you can just use the values you used to set your state and pass those to your file system for storage.
Let me know if that doesn't make enough sense.
I have a switch in my tab header and
I want to get the value of switch value in my header every time when I toggle the switch. how can I get that value?
const navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
title: "Home",
headerTitleStyle: {
flex: 1,
textAlign: "center",
color: "white",
fontSize: 20,
},
headerTintColor: "white",
headerStyle: {
backgroundColor: "#4169E1",
},
headerRight: (
<Switch
onValueChange={() => params.handleSave()}
value={this.navigation.state.switchValue}
/>
),
};
};
class HomeScreen extends React.Component {
state = { switchValue: false };
componentDidMount() {
this.props.navigation.setParams({ handleSave: this.toggleSwitch });
}
toggleSwitch = (value) => {
//onValueChange of the switch this function will be called
this.setState({ switchValue: value });
//state changes according to switch
//which will result in re-render the text
};
}
I just called this.props.navigation.setParams every time I update the params value in navigation options
toggleSwitch = (value) => {
this.setState({ switchValue: value });
this.props.navigation.setParams({
switchValue: holder,
});
};
I am trying to fetch some Data from an API and put them in a flatList. Each row in the flatList has a white star button which when pressing it would make this entry added to the favorites that are saved in AsyncStorage. What I want to do is to get the keys that are saved locally and check whether this item is in the favorites list I want to show a black star. I am not able to fill the favorites array in the state.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow
*/
import React, { Component } from 'react';
import {
StyleSheet, Text, View, ActivityIndicator, AsyncStorage,
Image, ToastAndroid, TouchableOpacity, FlatList
} from 'react-native';
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
isLoadingData: true,
dataSourceEmpty: null,
favorites: null
}
}
componentDidMount() {
return fetch('http://barcelonaapi.marcpous.com/bicing/stations.json')
.then((response) => response.json())
.then((JsonResponse) => {
this.setState({
isLoadingData: false,
dataSourceEmpty: JsonResponse.data.bici,
favorites: null
})
})
.catch((error) => {
console.log(error);
});
}
_touchListener = (item) => {
alert("ID is:" + item.id + "\n"
+ "Latitude is: " + item.lat + "\n"
+ "Longitude is:" + item.lon)
};
makeFavorite(item) {
this.saveData(item);
ToastAndroid.show(
'This station has been added to favorites!',
ToastAndroid.SHORT
);
}
saveData(station) {
AsyncStorage.setItem(station.id + "", station.name);
}
DATABASE_getAllBusStations = () => {
return new Promise(async (resolve, reject) => {
try {
let keys = await AsyncStorage.getAllKeys();
resolve(keys)
} catch (error) {
reject(new Error('Error getting keys from AsyncStorage: ' +
error.message))
}
});
}
checkifInDatabase = async () => {
try {
var keys = await this.DATABASE_getAllBusStations();
this.setState({
isLoadingData: true,
dataSourceEmpty: null,
favorites: keys
})
} catch (error) {
console.log(error);
}
}
render() {
if (this.state.isLoadingData) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" animating />
</View>
);
}
else {
return (
<View style={styles.container}>
<Text style={styles.header}>
BARCELONA BUS STATIONS
</Text>
<FlatList
data={this.state.dataSourceEmpty}
renderItem={({ item }) => {
let source = './Assets/fav.png';
// if(this.state.favorites.includes(item.id))
// {
// source = './Assets/favblack.png';
// }
return <TouchableOpacity style={styles.item}
onPress={() => this._touchListener(item)}>
<View style={styles.row}>
<Text style={styles.textStyle}>
{item.name}
</Text>
<View style={styles.buttonStyle}>
<TouchableOpacity onPress=
{() => this.makeFavorite(item)}>
<Image
source={require(source)}
style={styles.imageStyle}
/>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
}
}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
indicator: {
alignItems: 'center',
justifyContent: 'center',
},
row:
{
flexDirection: 'row',
justifyContent: 'space-between'
},
item: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#eee',
backgroundColor: 'skyblue'
},
header:
{
backgroundColor: '#F03209',
textAlign: 'center',
padding: 10,
color: 'skyblue',
fontSize: 20
},
imageStyle:
{
width: 50,
height: 50
},
textStyle:
{
padding: 10
}
});
You can either nest your calls or move them to an async function. See my example below.
import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';
interface ITestComponentState {
message_1: string;
message_2: string;
message_3: string;
message_4: string;
}
export default class TestComponent extends React.Component<RouteComponentProps<{}>, ITestComponentState> {
renders = 0;
constructor(props: any) {
super(props);
this.state = {
message_1: null,
message_2: null,
message_3: null,
message_4: null
}
}
componentDidMount() {
/**Using nested fetch calls**/
fetch('api/Local/GetData')
.then(response => response.text())
.then(message_1 => {
fetch('api/Local/GetData')
.then(response => response.text())
.then(message_2 => {
this.setState({ message_1, message_2 });
});
});
/**Using an async function**/
this.getMessages().then((messages: string[]) => this.setState({ message_3: messages[0], message_4: messages[1] }));
}
async getMessages() {
let message_3 = await fetch('api/Local/GetData').then(response => response.text());
let message_4 = await fetch('api/Local/GetData').then(response => response.text());
return [message_3, message_4];
}
public render() {
const { message_1, message_2, message_3, message_4 } = this.state;
this.renders++;
return (
<div>
<h1 className="test">Message 1: {message_1}</h1>
<h1 className="test">Message 2: {message_2}</h1>
<h1 className="test">Message 3: {message_3}</h1>
<h1 className="test">Message 4: {message_4}</h1>
<h2 className="test">Renders: {this.renders}</h2>
</div>
);
}
}
This one nests a fetch inside the first fetch.
fetch('api/Local/GetData')
.then(response => response.text())
.then(message_1 => {
fetch('api/Local/GetData')
.then(response => response.text())
.then(message_2 => {
this.setState({ message_1, message_2 });
});
});
This one puts them in an async method and calls the method inside componentDidMount.
this.getMessages().then((messages: string[]) => this.setState({ message_3: messages[0], message_4: messages[1] }));
async getMessages() {
let message_3 = await fetch('api/Local/GetData').then(response => response.text());
let message_4 = await fetch('api/Local/GetData').then(response => response.text());
return [message_3, message_4];
}
the results are:
Message 1: "Hello World!"
Message 2: "Hello World!"
Message 3: "Hello World!"
Message 4: "Hello World!"
Renders: 3
what about making the componentDidMount itself async?
async componentDidMount() {
try{
const response = await axios.get('http://barcelonaapi.marcpous.com/bicing/stations.json')
if(response.data.success){
this.setState({
isLoadingData: false,
dataSourceEmpty: response.data.bici,
favorites: null
})
}catch(error){
console.log(error);
}
}
I have a component will use map to render multi checkbox, and each checkbox has a callback function "onPress" get by props, the "onPress" function will setState checked, but now when I click on one checkbox, all checkboxs will be chosed, it cause they all use the same state, the goal I wanna choose each checkbox what I just ckick on, I know I can write many state different "onPress" function for each checkbox, but it looks stupid, I will add more checkbox in the future, What's the best and flexiable way to solve the task?
import React, { Component } from 'react'
import { View } from 'react-native'
import { CheckBox } from 'react-native-elements'
const styles = {
CheckBox: {
borderBottomWidth: 0.3,
borderBottomColor: 'gray'
},
checkBox : {
backgroundColor: "#ffffff",
borderWidth: 0
},
text: {
flex: 0.95,
backgroundColor: "#ffffff"
}
}
const languages = ["中文","英文","日文","韓文"]
class Language extends Component {
constructor(props) {
super(props);
this.state = { checked: false };
}
onPress = () => {
this.setState({ checked: !this.state.checked })
}
renderlanguages = () => {
return languages.map((langauge) => {
return(
<View key = { langauge } style = { styles.CheckBox }>
<CheckBox
title = { langauge }
iconRight
containerStyle = { styles.checkBox }
textStyle = { styles.text }
checkedColor = 'red'
checked = { this.state.checked }
onPress = { this.onPress }
/>
</View>
)
})
}
render(){
return(
<View>
{ this.renderlanguages() }
</View>
)
}
}
export default Language;
The behavior is choose all checkbox even though I only choose one now.
You can just pass the langauge (note this is probably a typo for language) variable to the function and us it to identify which one is being checked
onPress = (langauge) => {
this.setState({ [langauge]: { checked: !this.state[langauge].checked } })
}
renderlanguages = () => {
return languages.map((langauge) => {
return(
<View key = { langauge } style = { styles.CheckBox }>
<CheckBox
title = { langauge }
iconRight
//component = { () => {return <TouchableOpacity></TouchableOpacity>}}
containerStyle = { styles.checkBox }
textStyle = { styles.text }
checkedColor = 'red'
checked = { this.state[langauge].checked }
onPress = { () => this.onPress(langauge) }
/>
</View>
)
})
}