Expo : I want to render a Modal every x minutes - react-native

I want to render A modal every X minutes , so i tried to cache a value in AsyncStorage that gets removed every x minutes , and depends on the value i want to render the modal , but when my app is refreshed the modal appears again , here's what i have done :
import AsyncStorage from "#react-native-async-storage/async-storage";
import moment from "moment";
const prefix = "cache";
const expiryInMinutes = 5;
const store = async (key, value) => {
try {
const item = {
value,
timestamp: Date.now(),
};
await AsyncStorage.setItem(prefix + key, JSON.stringify(item));
} catch (error) {
console.log(error);
}
};
const isExpired = (item) => {
const now = moment(Date.now());
const storedTime = moment(item.timestamp);
return now.diff(storedTime, "minutes") > expiryInMinutes;
};
const get = async (key) => {
try {
const value = await AsyncStorage.getItem(prefix + key);
const item = JSON.parse(value);
if (!item) return null;
if (isExpired(item)) {
await AsyncStorage.removeItem(prefix + key);
return null;
}
return item.value;
} catch (error) {
console.log(error);
}
};
export default {
store,
get,
};
Then i have this component that i want to render every X minutes :
import React, { Component } from "react";
import { Text, TouchableOpacity, StyleSheet, View } from "react-native";
import Modal from "react-native-modal";
import AsyncStorage from "#react-native-async-storage/async-storage";
import cache from "../utility/cache";
export default class PubGlobal extends Component {
state = {
visibleModal: "false",
};
componentDidMount() {
cache.get("shown").then(
this.setState({
visibleModal: "true",
})
);
}
_renderButton = (text, onPress) => (
<TouchableOpacity onPress={onPress}>
<View style={styles.button}>
<Text>{text}</Text>
</View>
</TouchableOpacity>
);
_renderModalContent = () => (
<View style={styles.modalContent}>
<Text>Hello! </Text>
{this._renderButton("Close", () =>
cache
.store("shown", "false")
.then(this.setState({ visibleModal: "false" }))
)}
</View>
);
isShown = async () => {
try {
const stored = await cache.get("shown");
this.setState({ visibleModal: stored });
console.log(this.state.visibleModal);
} catch (error) {
console.log(error);
}
};
render() {
return (
<View style={styles.container}>
{/* {this._renderButton("Default modal", () =>
this.setState({ visibleModal: "true" })
)} */}
{this.state.visibleModal && (
<Modal isVisible={this.state.visibleModal === "true"}>
{this._renderModalContent()}
</Modal>
)}
</View>
);
}
}

In componentDidMount, after getting the value, visibleModal is set to "true".
You should use the value you are getting when cache.get("shown") resolves.
componentDidMount() {
cache.get("shown").then(
this.setState({
visibleModal: "true",
})
);
}

Related

dynamic textInput re-renders the whole component

It is part of a course so library is not an option. Basically, given a json object, generate a form. I can see the elements but I can't type in them. From my understanding, on each typing, the component is being rendered so the useState is being re-initalized. The only way I can type is if i remove the
value={formFields[id]}
from the TextInput.
https://snack.expo.io/#wusile/tenacious-fudge
Here is my code:
/* eslint-disable react/jsx-closing-bracket-location */
import React, { useContext, useState, useEffect } from 'react';
import { View, ScrollView, Text } from 'react-native';
import { TextInput, Button } from 'react-native-paper';
/*
Build a form dynamically from jsonData
see examples/sampleform.json file
*/
const defineFormFields = (data) => {
/*
Define the state to use along with default values.
*/
const stateData = {};
data.forEach((e) => {
stateData[e.id] = e.defaultValue || '';
});
return stateData;
};
const MyComponent = ({ jsonData }) => {
const [formFields, updateFormFields] = useState(defineFormFields(jsonData));
const [currentSegmentElements, updateCurrentViewElements] = useState([]);
const updateFormData = (fieldName, value) => {
const updatedValue = {};
updatedValue[fieldName] = value;
updateFormFields({
...formFields,
...updatedValue
});
};
const elementTypes = {
text(label, id) {
return (
<TextInput
key={id}
accessibilityHint={label}
label={label}
defaultValue={formFields[id]}
value={formFields[id]}
placeholder={label}
onChangeText={(value) => updateFormData(id, value)}
/>
);
}
};
const buildSegment = () => {
/*
Which segment/portion of the json to show
*/
const uiElements = [];
jsonData.forEach((e) => {
const definition = elementTypes[e.type](
e.label,
e.id
);
uiElements.push(definition);
});
updateCurrentViewElements(uiElements);
};
useEffect(() => {
buildSegment();
}, []);
return (
<ScrollView>
<View>
<View>
{currentSegmentElements.map((m) => m)}
</View>
</View>
</ScrollView>
);
};
const FormBuilder = React.memo(MyComponent);
export default FormBuilder;
Now where I need a form, I do:
const jsonData = [
{
"id":"FN",
"label":"FIrst Name",
"type":"text"
},
{
"id":"SN",
"label":"Last Name",
"type":"text"
},
{
"id":"countryCode",
"label":"Country Code",
"defaultValue":"US",
"type":"text"
}
]
<FormBuilder jsonData={jsonData} />
replace your useEffect by this.
useEffect(() => {
buildSegment();
}, [formFields]);

Screen State not Updating from AsyncStorage when going back

I'm building a React Native app.
My app has 5 Screens: Home (initialRouteName), DeckPage, QuestionPage, NewCardPage, NewDeckPage. (in this order)
I'm using Redux for state management. The state is updating from AsyncStorage.
The component that does the fetching is the class component "Home" by dispatching the "fetching" function in componentDidMount.
Component NewCardPage, NewDeckPAge are also updating the state with new content by dispatching the same fetching function as the Home when a button is pressed.
My problem appears when I want to delete a Deck component from inside DeckPage parent component. The function that does this job has this functionality: after removing the item from AsyncStorage, updates the STATE, and moves back to Screen HOME. The issue is that when I go back to HOME component the state doesn't update with the latest info from AsyncStorage.
This is not the case when I'm doing the same operation in the other 2 components NewCardPage, NewDeckPage.
I'll paste the code below:
import React, { Component } from "react";
import { connect } from "react-redux";
import { View, Text, StyleSheet, FlatList } from "react-native";
import Header from "../components/Header";
import AddDeckButton from "../components/AddDeckButton";
import DeckInList from "../components/DeckInList";
import { receiveItemsAction } from "../redux/actions";
class Home extends Component {
componentDidMount() {
this.props.getAsyncStorageContent();
}
renderItem = ({ item }) => {
return <DeckInList {...item} />;
};
render() {
const { items } = this.props;
// console.log(items);
const deckNumber = Object.keys(items).length;
return (
<View style={styles.container}>
<Header />
<View style={styles.decksInfoContainer}>
<View style={styles.deckNumber}>
<View style={{ marginRight: 50 }}>
<Text style={styles.deckNumberText}>{deckNumber} Decks</Text>
</View>
<AddDeckButton />
</View>
<View style={{ flex: 0.9 }}>
<FlatList
data={Object.values(items)}
renderItem={this.renderItem}
keyExtractor={(item) => item.title}
/>
</View>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getAsyncStorageContent: () => dispatch(receiveItemsAction()),
};
};
-----------DECKPAGE COMPONENT------------
import React from "react";
import { View, StyleSheet } from "react-native";
import Deck from "../components/Deck";
import { useSelector, useDispatch } from "react-redux";
import { removeItemAction, receiveItemsAction } from "../redux/actions";
import AsyncStorage from "#react-native-community/async-storage";
const DeckPage = ({ route, navigation }) => {
const { title, date } = route.params;
const questions = useSelector((state) => state.items[title].questions);
const state = useSelector((state) => state.items);
const dispatch = useDispatch();
// const navigation = useNavigation();
const handleRemoveIcon = async () => {
await AsyncStorage.removeItem(title, () => {
dispatch(receiveItemsAction());
navigation.goBack();
});
};
console.log(state);
return (
<View style={styles.deckPageContainer}>
<Deck
handleRemoveIcon={handleRemoveIcon}
title={title}
questions={questions}
date={date}
/>
</View>
);
};
-----------This is my ACTIONS file----------
import AsyncStorage from "#react-native-community/async-storage";
export const RECEIVE_ITEMS = "RECEIVE_ITEMS";
// export const REMOVE_ITEM = "REMOVE_ITEM";
export const receiveItemsAction = () => async (dispatch) => {
const objectValues = {};
try {
const keys = await AsyncStorage.getAllKeys();
if (keys.length !== 0) {
const jsonValue = await AsyncStorage.multiGet(keys);
if (jsonValue != null) {
for (let element of jsonValue) {
objectValues[element[0]] = JSON.parse(element[1]);
}
dispatch({
type: RECEIVE_ITEMS,
payload: objectValues,
});
} else {
return null;
}
}
} catch (e) {
console.log(e);
}
};
-----This is my REDUCERS file----
import { RECEIVE_ITEMS, REMOVE_ITEM } from "./actions";
const initialState = {
};
const items = (state = initialState, action) => {
switch (action.type) {
case RECEIVE_ITEMS:
return {
...state,
...action.payload,
};
// case REMOVE_ITEM:
// return {
// ...state,
// ...action.payload,
// };
default:
return state;
}
}
export default items;
-----This is my UTILS file----
import AsyncStorage from "#react-native-community/async-storage";
export const removeDeckFromAsyncStorage = async (title)=>{
try{
await AsyncStorage.removeItem(title);
}
catch(e){
console.log(`Error trying to remove deck from AsyncStorage ${e}`);
}
}

react native setInterval cannot read property apply

I am new in react native I am trying to render the count of unread notification for that I called my API in HOC it is working fine for initial few seconds but after that, I started to get the below error
func.apply is not a function
below is my code
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Modal, View } from "react-native";
import { themes } from "./constants";
import { AsyncStorage } from "react-native";
export default (OriginalComponent, animationType) =>
class extends Component {
static propTypes = {
handleFail: PropTypes.func,
theme: PropTypes.string,
visible: PropTypes.bool
};
state = {
modalVisible: true
};
static getDerivedStateFromProps({ visible }) {
if (typeof visible === "undefined") {
setInterval(
AsyncStorage.getItem("loginJWT").then(result => {
if (result !== null) {
result = JSON.parse(result);
fetch(serverUrl + "/api/getUnreadNotificationsCount", {
method: "GET",
headers: {
Authorization: "Bearer " + result.data.jwt
}
})
.then(e => e.json())
.then(function(response) {
if (response.status === "1") {
if (response.msg > 0) {
AsyncStorage.setItem(
"unreadNotification",
JSON.stringify(response.msg)
);
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}
})
.catch(error => {
alert(error);
// console.error(error, "ERRRRRORRR");
});
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}),
5000
);
return null;
}
return { modalVisible: visible };
}
handleOpenModal = () => {
this.setState({ modalVisible: true });
};
handleCloseModal = () => {
const { handleFail } = this.props;
this.setState({ modalVisible: false }, handleFail);
};
render() {
const { modalVisible } = this.state;
const { theme } = this.props;
return (
<View>
<Modal
animationType={animationType ? animationType : "fade"}
transparent={true}
visible={modalVisible}
onRequestClose={this.handleCloseModal}
>
<View style={themes[theme] ? themes[theme] : themes.transparent}>
<OriginalComponent
handleCloseModal={this.handleCloseModal}
{...this.props}
/>
</View>
</Modal>
</View>
);
}
};
I have not used getDerivedStateFromProps but, according to the docs, it is called on initial component mount and before each render update.
Thus your code is creating a new interval timer on each update without clearing any of the earlier timers, which could be causing a race condition of some sort.
You may want to consider using the simpler alternatives listed in the docs, or at a minimum, insure that you cancel an interval before creating a new one.

Lodash debounce not working all of a sudden?

I'm using a component I wrote for one app, in a newer app. The code is like 99% identical between the first app, which is working, and the second app. Everything is fine except that debounce is not activating in the new app. What am I doing wrong?
// #flow
import type { Location } from "../redux/reducers/locationReducer";
import * as React from "react";
import { Text, TextInput, View, TouchableOpacity } from "react-native";
import { Input } from "react-native-elements";
import { GoogleMapsApiKey } from "../../.secrets";
import _, { debounce } from "lodash";
import { connect } from "react-redux";
import { setCurrentRegion } from "../redux/actions/locationActions";
export class AutoFillMapSearch extends React.Component<Props, State> {
textInput: ?TextInput;
state: State = {
address: "",
addressPredictions: [],
showPredictions: false
};
async handleAddressChange() {
console.log("handleAddressChange");
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GoogleMapsApiKey}&input=${this.state.address}`;
try {
const result = await fetch(url);
const json = await result.json();
if (json.error_message) throw Error(json.error_message);
this.setState({
addressPredictions: json.predictions,
showPredictions: true
});
// debugger;
} catch (err) {
console.warn(err);
}
}
onChangeText = async (address: string) => {
await this.setState({ address });
console.log("onChangeText");
debounce(this.handleAddressChange.bind(this), 800); // console.log(debounce) confirms that the function is importing correctly.
};
render() {
const predictions = this.state.addressPredictions.map(prediction => (
<TouchableOpacity
style={styles.prediction}
key={prediction.id}
onPress={() => {
this.props.beforeOnPress();
this.onPredictionSelect(prediction);
}}
>
<Text style={text.prediction}>{prediction.description}</Text>
</TouchableOpacity>
));
return (
<View>
<TextInput
ref={ref => (this.textInput = ref)}
onChangeText={this.onChangeText}
value={this.state.address}
style={[styles.input, this.props.style]}
placeholder={"Search"}
autoCorrect={false}
clearButtonMode={"while-editing"}
onBlur={() => {
this.setState({ showPredictions: false });
}}
/>
{this.state.showPredictions && (
<View style={styles.predictionsContainer}>{predictions}</View>
)}
</View>
);
}
}
export default connect(
null,
{ setCurrentRegion }
)(AutoFillMapSearch);
I noticed that the difference in the code was that the older app called handleAddressChange as a second argument to setState. Flow was complaining about this in the new app so I thought async/awaiting setState would work the same way.
So changing it to this works fine (with no flow complaints for some reason. maybe because I've since installed flow-typed lodash. God I love flow-typed!):
onChangeText = async (address: string) => {
this.setState(
{ address },
_.debounce(this.handleAddressChange.bind(this), 800)
);
};

react native modal not close after setState false

I have set modal visibility to false but it still showing. I cant figure out what causes this issue. this my code at loading.js.
I'm use this component in main what happen when setState false but its just close after close simolator and restart the device
import React,{Component} from 'react';
import PropTypes from 'prop-types'
import {View, Image, Modal, StyleSheet, Text} from "react-native";
export default class Loader extends Component{
render(){
const {animationType,modalVisible}=this.props;
return(
<Modal
animationType={animationType}
transparent={true}
visible={modalVisible}>
<View style={styles.wrapper}>
<View style={styles.loaderContainer}>
<Image
source={require('../img/loading.gif')}
style={styles.loaderImage}/>
</View>
</View>
</Modal>
)
}
}
Loader.propTypes={
animationType:PropTypes.string.isRequired,
modalVisible:PropTypes.bool.isRequired
}
this main class
export default class ForoshRah extends Component {
constructor() {
super();
I18nManager.forceRTL(true);
this.state = {
image: null,
images: null,
loadingVisible:false,
};
this.onValueChange2=this.onValueChange2.bind(this);
this.OnSubmiteData=this.OnSubmiteData.bind(this);
}
onValueChange2(value: string) {
this.setState({
Field: value,
});
}
async OnSubmiteData(){
this.setState({loadingVisible:true})
let token = await AsyncStorage.getItem('token',token);
let response = await
fetch(url,{
method:'POST',
headers:{
'Content-Type':'application/json',
Authorization:'JWT'+" "+token,
}
,body: JSON.stringify({
title,
})
})
let register = await response.json();
this.setState({userID:register.id})
if(response.status===200){
this.UploadImage()
}
}
async UploadImage() {
let token = await AsyncStorage.getItem('token',token);
let response = await fetch(url,{
method:'POST',
headers:{
Authorization:'JWT'+" "+token,
},body: formData
})
let uimage = await response;
console.log('user',this.state.userID);
if(response.status=200){
handleCloseModal = () => {
console.log(this.state.loadingVisible);
this.setState({ loadingVisible: false})
});
};
this.props.navigation.dispatch({ type: 'Navigation/BACK' })
}else {
setTimeout(() => {
this.setState({ loadingVisible: false })
}, 100)
}
setTimeout(() => {
this.setState({ loadingVisible: false })
}, 100)
}
render() {
return (
<KeyboardAwareScrollView >
<View style={{marginBottom:'10%'}}>
<Button block style={{backgroundColor:'#8e25a0'}} onPress={this.OnSubmiteData.bind(this)}>
</Button>
</View>
<Loader
modalVisible={loadingVisible}
animationType="fade"
/>
</KeyboardAwareScrollView>
);
}
}
onsubmitdata setState true and after response going to 200 Setstate set false in code main
You cannot just call state name as you have did. You should do like below.
<Loader
modalVisible={this.state.loadingVisible}
animationType="fade"
/>