How to Control async method react-native - react-native

I want to send data to another component. I get datas from
AsyncStorage.getItem('myKey');
But when start async, component start to render so it sends null data to another component.
here is my methods ;
componentWillMount(){
this.getdata();
}
getdata = async () => {
console.log("console1");
const value = await AsyncStorage.getItem('myKey');
console.log("console2");
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}
and this is my render method ;
render() {
console.log("rende splashscreen: ", this.state.values);
let { fadeAnim } = this.state;
return (
<View style = {{flex:1}}>
<LoginForm profile = {this.state.values}/>
<Animated.View style={{ ...this.props.style, opacity: fadeAnim }} >
{this.props.children}
<ImageBackground style={styles.logo1} source={require('../../image/dataLogo.jpeg')} >
</ImageBackground>
</Animated.View>
</View>
);
}
I send datas to LoginForm. I want to ask one more question. If I use <LoginForm /> like this, it ruins my component. How can I send with different way ?

Only render if it's ready to render. the way I do it is initialize a state lets say isReady and set to false then set it to true when you have the value.
Would look like this:
export default class test extends Component {
constructor(props) {
super(props)
this.state = {
isReady:false
}
}
componentWillMount(){
this.getdata();
}
getdata = async () => {
const value = await AsyncStorage.getItem('myKey');
this.setState({isReady:true})
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}
render() {
if(this.state.isReady){
return (
<View ref={ref => this.view = ref} onLayout={() => this.saveLayout()}>
</View>
)}else{
<View></View>
}
}
}
To your second question:
If you pass through LoginForm you can create a function there that gets the parameters and updates state, then pass that function to your other component and call the function with the values in paremeter. if you are using react navigation you can do it like so:
loginForm
updateValues(values){
this.setState({value:values})
}
To pass the function with react-navigation:
this.props.navigation.navigate('otherComponent',{updateValues:this.updateValues})
In your otherComponent you call the function like so:
otherComponent
this.props.navigation.state.params.updateValues(newValues);

How about checking for the values variable in the render method?
render() {
console.log("rende splashscreen: ", this.state.values);
let { fadeAnim } = this.state;
return (
this.state.values ?
<View style = {{flex:1}}>
<LoginForm profile = {this.state.values}/>
<Animated.View style={{ ...this.props.style, opacity: fadeAnim }} >
{this.props.children}
<ImageBackground style={styles.logo1} source={require('../../image/dataLogo.jpeg')} >
</ImageBackground>
</Animated.View>
</View>
: <></>
);
}

You can keep a default/initial values in state variable at first like this:
constructor(props){
this.state = {
values:{userName: '', password: ''}
}
}
And when the actual values are available you can set them in state and automatically re-rendering will occur.
Since AsyncStorage returns a promise you can use .then() syntax
componentDidMount(){
console.log("console1");
AsyncStorage.getItem('myKey').then(value=>{
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}).catch(err=>{
console.log('err', err);
})
}

Related

Is it possible to render/return React Native elements from functions?

so i want to load some data from my server using axios in React native. The data was retrieved successfully, but i don't know how to display it on the page. When i click button 'Load students' it does axios get method and after that calls method 'showStudents' but that method doesn't return anything. I really don't understand how rendering works in react native so i would appreciate any help and guidance. Also if there is easier way to do all of this, i'm open for suggestions.
export default function Students() {
const [s, setStudents] = useState('')
const getStudents = async () => {
try{
const {data: {students}} = await axios.get('http://192.168.1.2:3000/api/v1/students')
setStudents(students)
//console.log(students)
showStudents()
}
catch(error){
console.log(error)
}
}
const showStudents = () => {
return( <ScrollView>
{
s.map((student) => (
<ListItem key={student._id} bottomDivider>
<ListItem.Content>
<ListItem.Title>{student.firstName}</ListItem.Title>
<ListItem.Subtitle>{student.index}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
))
}
</ScrollView>)
}
return (
<View style={styles.container}>
<Button title='Load students' color='green' onPress={getStudents}/>
</View>
);
}
The function showStudents returns a JSX component, but not inside of the render function of the component Students.
You can just create a new JSX component and use conditional rendering in order to render it whenever the state s (I would call it students) is not undefined and has a length strictly greater than zero.
const [students, setStudents] = useState()
const getStudents = async () => {
try{
const {data: {students}} = await axios.get('http://192.168.1.2:3000/api/v1/students')
setStudents(students)
}
catch(error){
console.log(error)
}
}
return (
<View style={styles.container}>
<Button title='Load students' color='green' onPress={getStudents}/>
{
students && students.length > 0 ? <ScrollView>
{
students.map((student) => (
<ListItem key={student._id} bottomDivider>
<ListItem.Content>
<ListItem.Title>{student.firstName}</ListItem.Title>
<ListItem.Subtitle>{student.index}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
))
}
</ScrollView> : null
}
</View>
);
We could create a new component to make things more structured. Let us introduce StudentList.
export function StudentList({students}) {
return <ScrollView>
{
students.map((student) => (
<ListItem key={student._id} bottomDivider>
<ListItem.Content>
<ListItem.Title>{student.firstName}</ListItem.Title>
<ListItem.Subtitle>{student.index}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
))
}
</ScrollView>
}
Then, reuse this new component.
const [students, setStudents] = useState()
const getStudents = async () => {
try{
const {data: {students}} = await axios.get('http://192.168.1.2:3000/api/v1/students')
setStudents(students)
}
catch(error){
console.log(error)
}
}
return (
<View style={styles.container}>
<Button title='Load students' color='green' onPress={getStudents}/>
{
students && students.length > 0 ? <StudentList students={students} /> : null
}
</View>
);

Counter with Async Storage in React Native

I am new to React Native.
I want to make a counter using Async storage in React Native Expo.
Async storage works with string value but I need to use integer value and can't find an example to create it.
I would appreciate it if you suggest with SQLite or if there is a different storage area.
storeData = async (counter) => {
try {
await AsyncStorage.setItem('', counter)
} catch (e) {
}
}
getData = async () => {
try {
const value = await AsyncStorage.getItem('counter')
if(counter !== null) {
}
} catch(e) {
}
}
render() {
return(
<SafeAreaView style={styles.container}>
<ImageBackground style={styles.image}>
<View style={{marginBottom: 250}}>
<Text style={styles.counter}>{counter}</Text>
</View>
<TouchableOpacity
style={styles.floatingButton1}
onPress={this.onAddCounter}>
<Text style={{fontSize:13, color:"white", fontWeight:"600"}}>Tap to Counter</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.resetButton1}
onPress={this.onReset1}>
<Icon name="undo" size={20} color="#900"/>
</TouchableOpacity>
</ImageBackground>
</SafeAreaView>
);
}
}
You can convert the integer to a string when you store the value:
number.toString()
And convert it to integer when you retrieve the value
parseInt(string)
Basically it will become
storeData = async (counter) => {
try {
await AsyncStorage.setItem('counter', counter.toString())
} catch (e) {
}
}
getData = async () => {
try {
const value = await AsyncStorage.getItem('counter')
if(counter !== null) {
value = parseInt(value)
}
} catch(e) {
}
}
Use JSON.parse for values getting from AsyncStorage
https://react-native-async-storage.github.io/async-storage/docs/usage/#reading-object-value

Update props from other component in react native

I have a Main class which I show an array to user, then in detail page user can edit each element which I'm passing using react navigation parameter. I want to edit my array in the detail class and save it using async storage.
//Main.jsimport React from 'react';
import {
StyleSheet ,
Text,
View,
TextInput,
ScrollView,
TouchableOpacity,
KeyboardAvoidingView,
AsyncStorage
} from 'react-native'
import Note from './Note'
import detail from './Details'
import { createStackNavigator, createAppContainer } from "react-navigation";
export default class Main extends React.Component {
static navigationOptions = {
title: 'To do list',
headerStyle: {
backgroundColor: '#f4511e',
},
};
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
dueDate: ''
};
}
async saveUserTasks(value) {
try {
await AsyncStorage.setItem('#MySuperStore:userTask',JSON.stringify(value));
} catch (error) {
console.log("Error saving data" + error);
}
}
getUserTasks = async() =>{
try {
const value = await AsyncStorage.getItem('#MySuperStore:userTask');
if (value !== null){
this.setState({ noteArray: JSON.parse(value)});
}
} catch (error) {
console.log("Error retrieving data" + error);
}
}
render() {
this.getUserTasks()
let notes = this.state.noteArray.map((val,key) => {
return <Note key={key} keyval={key} val={val}
deleteMethod={ () => this.deleteNote(key)}
goToDetailPage= {() => this.goToNoteDetail(key)}
/>
});
const { navigation } = this.props;
return(
<KeyboardAvoidingView behavior='padding' style={styles.keyboard}>
<View style={styles.container}>
<ScrollView style={styles.scrollContainer}>
{notes}
</ScrollView>
<View style={styles.footer}>
<TextInput
onChangeText={(noteText) => this.setState({noteText})}
style={styles.textInput}
placeholder='What is your next Task?'
placeholderTextColor='white'
underlineColorAndroid = 'transparent'
>
</TextInput>
</View>
<TouchableOpacity onPress={this.addNote.bind(this)} style={styles.addButton}>
<Text style={styles.addButtonText}> + </Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
}
addNote(){
if (this.state.noteText){
var d = new Date();
this.state.noteArray.push({
'creationDate': d.getFullYear() + "/" + (d.getMonth()+1) + "/" + d.getDay(), 'taskName': this.state.noteText,'dueDate':'YYYY/MM/DD'
});
this.setState({noteArray:this.state.noteArray})
this.setState({noteText: ''});
this.saveUserTasks(this.state.noteArray)
}
}
deleteNote(key){
this.state.noteArray.splice(key,1);
this.setState({noteArray: this.state.noteArray})
this.saveUserTasks(this.state.noteArray)
}
goToNoteDetail=(key)=>{
this.props.navigation.navigate('DetailsScreen', {
selectedTask: this.state.noteArray[key],
});
}
}
in detail view I have this method which is similar to add note in main class:
export default class Details extends React.Component {
render() {
const { navigation } = this.props;
const selectedTask = navigation.getParam('selectedTask', 'task');
return(
<View key={this.props.keyval} style={styles.container}>
<TouchableOpacity onPress={this.saveEdit.bind(this)} style={styles.saveButton}>
<Text style={styles.saveButtonText}> save </Text>
</TouchableOpacity>
</View>
);
}
saveEdit(){
let selectedItem = { 'creationDate': selectedTask['creationDate'],
'taskName': selectedTask['taskName'],
'dueDate': this.state.dueData}
this.props.navigation.state.params.saveEdit(selectedItem)
}
}
How can I change my props in any component?
First of all you shouldn't call this.getUserTasks() in the render method because the function has this.setState which is bad and could end in a endless loop I guess or at least effect in worse performance. You could instead call it in componentDidMount:
componentDidMount = () => {
this.getUserTasks();
}
Or alternatively call already in constructor but I prefer the first option:
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
dueDate: ''
};
this.getUserTasks()
}
this.props.noteArray.push({.. is probably undefined because you aren't passing it down any where. (Didn't see any reference in your snippet). I guess I would implement the saveEdit function in the Main.js component and simply pass it down to the navigation route and call the function in Details component by accessing the navigation state props:
Update
goToNoteDetail=(key)=>{
this.props.navigation.navigate('DetailsScreen', {
// selectedTask: this.state.noteArray[key],
selectedItem: key,
saveEdit: this.saveEdit
});
}
saveEdit(selectedItem){
const selectedTask = this.state.noteArray[selectedItem]
this.state.noteArray.push({
'creationDate': selectedTask['creationDate'],
'taskName': selectedTask['taskName'],
'dueDate': this.state.dueData
});
this.setState({noteArray:this.state.noteArray})
this.setState({dueData: 'YYYY/MM/DD'});
this.saveUserTasks(this.state.noteArray)
}
And then call saveEdit in Details Component:
saveSelectedItem = () => {
const { navigation } = this.props.navigation;
const {selectedItem, saveEdit} = navigation.state && navigation.state.params;
saveEdit(selectedItem)
}

Mobx observer is not reacting to changes in observable

After action call, the observable gets updated with new values but it doesn't trigger update in my observer class. The reaction occurs when I put an condition check in render which uses observable object. But I use observable object in another place inside the returned DOM as a prop condition. I couldn't understand why this happens.
Here is my observer class
#inject("store")
#observer
export default class SignupWithMobileNo extends Component {
constructor() {
super();
this.sendOTP = this.sendOTP.bind(this);
this.state = {
phoneInput: ""
};
}
static navigationOptions = {
header: null
};
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackButton);
}
handleBackButton() {
ToastAndroid.show("You cannot go back", ToastAndroid.SHORT);
return true;
}
sendOTP(phone) {
this.props.store.userStore.sendOTP(phone);
}
componentDidUpdate() {
console.log("component did update", this.props);
const navigation = this.props.navigation;
const { sendOTPRequest } = this.props.store.userStore;
if (sendOTPRequest.state === "succeeded") {
navigation.navigate("VerifyOTP");
}
}
render() {
const navigation = this.props.navigation;
const { sendOTPRequest } = this.props.store.userStore;
// reaction occurs when I uncomment the following lines.
// if (sendOTPRequest.state === "succeeded") {
// }
return(
<View style={styles.container}>
<Formik
initialValues={{
phone: ""
}}
onSubmit={values => {
this.sendOTP(values.phone);
}}
validate={values => {
let errors = {};
if (values.phone.length < 1) {
errors.phone = "Invalid phone number";
}
return errors;
}}
>
{({
handleChange,
handleSubmit,
setFieldTouched,
values,
errors,
touched
}) => (
<View style={styles.formBody}>
<Text style={styles.headline}>Get authenticate your account</Text>
<FormInput
onChange={handleChange("phone")}
value={values.phone}
placeholder="Enter your phone number"
keyboardType="phone-pad"
onBlur={() => {
setFieldTouched("phone");
}}
/>
<FormButton
onClickHandler={handleSubmit}
buttonText="Send OTP"
isDisabled={
values.phone.length < 1 ||
sendOTPRequest.state === "requested"
}
/>
{touched.phone && errors.phone ? (
<Text style={styles.body}> {errors.phone} </Text>
) : null}
{sendOTPRequest.state === "failed" ? (
<Text style={styles.body}> {sendOTPRequest.error_code</Text>
) : null}
</View>
)}
</Formik>
</View>
);
}
}
No subscribers to observable data in the observer's render function. Once I added that, the issue solved.

React native Unable to render the page with force update or set state

class Wait extends Component{
constructor(props) {
super(props);
this.state = { fetchingData: true, data: [], check: ''}
this.forceUpdateHandler.bind(this);
}
getData = async() => {
try {
data = await AsyncStorage.getItem('restaurants');
if (data != null) {
this.setState({fetchingData: false , data: JSON.parse(data)})
}
} catch(error){
console.log(error)
}
}
forceUpdateHandler(){
this.forceUpdate();
};
componentDidMount(){
this.getData();
}
renderRestaurant(){
return this.state.data.map((item) => {
return (
<View style ={{marginTop: 20, backgroundColor: 'red', marginTop: 20 }}>
<Text> {item.name} </Text>
<Text> {item.time} </Text>
<Text> {item.wait} </Text>
<Text> {item.people} </Text>
<Button title = 'cancel' onPress = { async () => {
let data = await AsyncStorage.getItem('restaurants');
let temp = JSON.parse(data)
let i = -1
temp.map((value, index) => {
if (value.name == item.name){
i = index;
}
})
if (i > -1){
temp.splice(i, 1)
await AsyncStorage.setItem('restaurants', JSON.stringify(temp))
}
this.forceUpdateHandler() // First way
this.forceUpdate() // Second way
this.setState({check: 'checked'}) // Third way
}
}
/>
</View>
)
})
}
render(){
const { navigate } = this.props.navigation;
const { navigation } = this.props;
return (
<View style={{width:200, height:200, justifyContent:'center', alignItems:'center', }}>
{this.state.fetchingData ? null : this.renderRestaurant()}
</View>
)
}
}
I am trying to make the page re-render each time after I click the button. Once click the button, it access the AsyncStorage and delete the corresponding element in the array, then it update the AsyncStorage with the new array and re-render the page.
I have tried the following:
1) call forUpdate directly after the update of the AsyncStorage
2) define the forceUpdateHandler function and bind it with this
3) call this.setState after the update of the AsyncStorage
But none of the above options re-renders the page. Can someone help to fix it? An example would be great! Thanks in advance.
The answer is simple. It doesn't re-render because it has nothing to re-render. It calls the render, check each component in the render if the data used to render it has changed and render them if needed. If you look at your code, you see that on the button press, you save in the async storage the new data. However, your rendering uses this.state.data to render the item. The problem is that you never update the state of your component with the new data.
Sure, you do this.setState({check: 'checked'}), but nothing in the render is using it. So there's no point in updating the UI.
An easy way to fix it would be to call this.getData() at the end of the button onPress. That way, you would update the data state which would update your UI.
Get the updated list of restaurants { removing the selected restaurant}
Stored the updated list to Asyncstorage.
fetch the updated list from asyncStorage and set the state.
storeData = async (restaurants) => {
try {
await AsyncStorage.setItem('restaurants', JSON.stringify(restaurants))
} catch (error) {
console.log("Error", error);
}
}
renderRestaurant(){
return this.state.data.map((item, index, restaurants) => {
return (
<View key={index}>
<Text> {item.name} </Text>
<Text> {item.time} </Text>
<Button
title = 'cancel'
onPress = {() => {
let restaurantListWithoutCurrentRestaurant = restaurants.filter((restaurant)=> restaurant.name !== item.name);
this.storeData(restaurantListWithoutCurrentRestaurant);
this.getData();
}}/>
</View>
)
})
}
I think this will solve your problem.