Why is AsyncStorage getAllKeys not returning null? Have multiple views, want to render based on if data found, deleted all keys - react-native

I have a boolean called isDataReady stored in the state. If I find keys via AsyncStorage, I set it true and display a list of data. If nothing is found then I want to render a different view. My data is displaying fine but with everything deletef, I can't get my intro screen to display. Its because AsyncStorage is never null despite their being no keys. What am I doing wrong?
Code (view related code removed for clarity)
constructor() {
super();
this.state={
meals: [],
isDataReady: false,
}
}
componentDidMount() {
this.getAllMeals();
}
getAllMeals = async () => {
try {
const data = [];
let keys = await AsyncStorage.getAllKeys();
// await AsyncStorage.multiRemove(keys);
if (keys !== null) {
for (let inKey of keys) {
let obj = await AsyncStorage.getItem(inKey);
obj = JSON.parse(obj);
obj["key"] = inKey;
data.push(obj);
}
this.setState({
meals: data,
isDataReady: true
})
} else {
this.setState({
isDataReady: false
})
}
} catch (error) {
console.log("Error saving all meals. Error: " + error)
}
}
render() {
if (this.state.isDataReady === true) {
return (
<View style={styles.container}>
</View>
);
} else if (this.state.isDataReady === false) {
return (
<ScrollView>
<View style={styles.container}>
</View>
</ScrollView>
);
}
}
}

I change the if statement to if (keys.length !== 0), always returns array so its never null.

Related

Render in React Native interferes with variable update

I have the following code written in React Native. As can be seen, a function within 'componentDidMount' is called ('getKey') that is used to retrieve some variables previously saved from storage:
export default class Cast extends Component {
constructor(props) {
super(props);
this.state = {
admin: false,
isPublishing: false,
userComment: "",
hasPermission: false,
paused: true,
_email: false,
_name: false,
_pword: false,
_play_ID: false,
_streamkey: false,
_playurl: "",
_streamurl: "",
isLoading : true,
};
}
getKey = async() => {
try {
var value = await AsyncStorage.getItem('email');
this.setState({ _email: value });
value = await AsyncStorage.getItem('playkey');
this.setState({ _play_ID: value });
const playurl = "https://stream.mux.com/" + value + ".m3u8"
this.setState({ _playurl: playurl });
value = await AsyncStorage.getItem('castkey');
this.setState({ _streamkey: value });
const streamurl = "rtmp://global-live.mux.com:5222/app/" + value
this.setState({ _streamurl: streamurl });
this.setState({ isLoading: false });
} catch (error) {
console.log("Error retrieving data" + error);
}
}
componentDidMount(){
this.getKey();
}
renderCameraView = () => {
return (
<NodeCameraView
style={styles.nodeCameraView}
/* eslint-disable */
ref={vb => {
this.vb = vb;
}}
/* eslint-enable */
outputUrl = {this.state._streamurl}
camera={settings.camera}
audio={settings.audio}
video={settings.video}
autopreview
/>
);
};
renderPlayerView = () => {
const { paused } = this.state;
const source = {
uri: _playurl //THROWS A "VARIABLE NOT FOUND" ERROR...LIKELY DUE TO RENDER BEFORE VALUE IS RETREIVED FROM STORAGE...?
};
return (
<Video
source={source} // Can be a URL or a local file.
/* eslint-disable */
ref={ref => {
this.player = ref;
}} // Store reference
/* eslint-enable */
onBuffer={this.onBuffer} // Callback when remote video is buffering
onError={this.onError} // Callback when video cannot be loaded
style={styles.nodePlayerView}
fullscreen={false}
resizeMode="cover"
paused={paused}
/>
);
};
renderEmptyView = () => {
const { paused } = this.state;
const source = {
uri: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"
};
return (
<Video
source={source} // Can be a URL or a local file.
/* eslint-disable */
ref={ref => {
this.player = ref;
}} // Store reference
/* eslint-enable */
onBuffer={this.onBuffer} // Callback when remote video is buffering
onError={this.onError} // Callback when video cannot be loaded
style={styles.nodePlayerView}
fullscreen={false}
resizeMode="cover"
paused={paused}
/>
);
};
//...
render() {
const { admin, paused, isPublishing } = this.state;
return (
<View style={styles.container}>
{isLoading ? this.renderEmptyView() : !isLoading && admin ? this.renderPlayerView() : !isLoading && !admin ? this.renderCameraView()}
//...
</View>
);
//...
This code is mainly operative. The problem is that the function 'renderPlayerView' is immediately called/rendered...as can be seen in the 'render' section at the bottom of the code. As I understand...in React Native any render is performed BEFORE any other process. I believe this is the cause of my problem.
The 'uri: _playurl' line within the 'renderPlayerView' function throws a 'variable not found' error or something similar. Since the render is performed before anything else, I guess this makes sense to me as the value of the '_playurl' variable would not have yet been retrieved from storage.
Therefore my question is how could I pass the correct value (after retrieved from 'storage') to the '_playurl' variable FOLLOWING the render? Or perhaps there is some sort of work-around? I thank you in advance for any suggestions.
You should create a loader for this, just create a state and set its initial value to true and once you're done fetching the data from async storage set it to false. Then you should use this state to conditionally render the whole component.
like this:-
// 1. Create state
state: {
...rest of your states,
isLoading : true // ADD this
}
//2. Update state of isLoading once you are done fetching data
getKey = async() => {
try {
... your code
} catch (error) {
console.log("Error retrieving data" + error);
} finally {
this.setState({ loading: false });
}
}
//3. use the state to render your components conditionally
render() {
const { admin, paused, isPublishing, isLoading } = this.state;
return (
<View style={styles.container}>
{isLoading ? <Loader/> //create some loader for your app (or simply use an activity indicator)
: admin ? this.renderCameraView() : this.renderPlayerView()}
//...
</View>
);

How to get AsyncStorage key name from FlatList item to delete?

I am generating a random key name for AsyncStorage each time user saves an item. These are then displayed in FlatList (using SwipeListView library for swipe to delete button). Now if I call await AsyncStorage.removeItem(key); when the user taps "Delete", I presume the item will just disappear from the list. What I'm completely lost on is how I am supposed to get my random key name? Struggling to find much on FlatList and AsyncStorage, not sure what good practice is.
FlatList:
export default class RecentMealsScreen extends Component {
constructor() {
super();
this.state={
meals: []
}
}
componentDidMount() {
this.getAllMeals();
}
getAllMeals = async () => {
try {
const data = [];
let keys = await AsyncStorage.getAllKeys();
for (let inKey of keys) {
let obj = await AsyncStorage.getItem(inKey);
obj = JSON.parse(obj);
data.push(obj);
}
this.setState({
meals: data
})
} catch (error) {
console.log("Error saving all meals. Error: " + error)
}
}
renderHiddenItem = () => (
<View style={styles.rowBack}>
<View style={[styles.backRightBtn, styles.backRightBtnRight]}>
<Text style={styles.backTextWhite}>Delete</Text>
</View>
</View>
);
deleteMeal = async (key) => {
try {
await AsyncStorage.removeItem(key);
} catch (error) {
console.log('Error deleting Meal: ' + error)
}
}
// Get Meal IDs and display them in list
render() {
return (
<View style={styles.container}>
<SwipeListView
data={this.state.meals}
renderItem={ ({item}) =>
<View style={styles.container}>
<Meal
image = {item.image}
order={item.orderName}
company={item.companyName}
price={item.price}
dateTime={item.dateTime}
notes={item.notes}
rating = {item.rating}
/>
</View>
}
disableRightSwipe
renderHiddenItem={this.renderHiddenItem}
rightOpenValue={-Dimensions.get('window').width}
useNativeDriver={false}
onSwipeValueChange={this.deleteMeal()}
/>
</View>
);
}
}
Save Logic:
saveMeal = async () => {
try {
let meal = {
image: this.state.imageSource,
orderName: this.state.orderText,
companyName: this.state.selectedCompany,
price: this.state.priceText,
dateTime: this.state.dateTimeText,
notes: this.state.notesTextField,
rating: this.state.starCount
};
const ID = await Random.getRandomBytesAsync(16);
await AsyncStorage.setItem(ID.toString(), JSON.stringify(meal)).then(() => {
// Redirect to new screen
Actions.recentMeals();
})
} catch (error) {
console.log("Save Meal error: " + error)
}
}

How to properly use AsyncStorage in a Movie WatchList

First of all,i tried to make a watchlist for movies,a page that contains movies that you add.I can't get understand async storage and how it works within my code.
I've tried different tutorials,but I still don't get to make it work.I tried with the code that official react-native page has on their site,but with no results.
item2ADD = this.props.navigation.getParam('movie');
_retrieveData = async () => {
try {
await AsyncStorage.setItem('#MySuperStore:key', 'I like to save it.');
} catch (error) {
console.warn("data has not been saved");
}
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
console.warn("data has been loaded");
console.warn(value);
}
} catch (error) {
console.warn("data has not been loaded");
}
};
constructor(props) {
super(props);
this.state = {
data : [] ,
};
}
async componentDidMount() {
const data = this._retrieveData();
this.setState({ data });
}
render() {
if(this.state.data!==null) {
return (
<View style={styles.container}>
<Text style={styles.title}>Watchlist</Text>
<TouchableOpacity style={styles.backBtn} onPress={() => {
this.props.navigation.goBack()
}}>
<Image style={styles.backIMG} source={menuImages.back}/>
</TouchableOpacity>
<FlatList
data={this.state.data}
numColumns={2}
renderItem={({item}) =>
<View style={styles.fList}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('Details', {movie: item})}>
<Image style={styles.img} source={{uri: item.coverUrl}}/>
</TouchableOpacity>
<Text style={styles.movieTitle}>{item.title}</Text>
<Text
style={styles.movieDate}>{moment.unix(Math.floor(parseInt(item.releaseDate)) / 1000).format("DD/MM/YYYY")}</Text>
</View>
} keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
else
{
return <Text>FAILED TO LOAD</Text>
}
}
}
I just want to know how can I implement AsyncStorage to store my added movies from the item2ADD,it contains only 1 item that i need to store in order to make a watchlist.(the item which contains all details that i need for a movie).
I want to save all the movies with asyncstorage then show them in that flatlist.
I keep getting :
invariant violation tried to get frame for out of range index nan(on android simulator) when i click to add a movie to the list.
I'm not sure what is the problem you are asking but there is a few things that maybe causing the problem.
1- In your function _retrieveData you don't return anything from it.
I'm not sure what you want to return but what you could do is return the value from the AsyncStorage.
_retrieveData = async () => {
try {
await AsyncStorage.setItem('#MySuperStore:key', 'I like to save it.');
} catch (error) {
console.warn("data has not been saved");
}
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
console.warn("data has been loaded");
console.warn(value);
}
// returning the value.
return value
} catch (error) {
console.warn("data has not been loaded");
}
};
2- You are setting state with the data from _retrieveData without waiting for it.
In you componentDidMount you set the data from _retrieveData but because it's a promise, you need to use await to get the data
async componentDidMount() {
// added await
const data = await this._retrieveData();
this.setState({ data });
}
3- The data you provide to FlatList needs to be an array.
When you call pass data={this.state.data} to FlatList, it needs to be an array, if it's not an array, you will have several problems.
Make sure that when you return the value from _retrieveData, it's an array.
// value needs to be an array
const value = await AsyncStorage.getItem('TASKS');
You should also notice that to setItem, it need to be parsed to a json, this means doing something like AsyncStorage.setItem('NAME_OF_YOUR_KEY', JSON.stringify(DATA_YOU_WANT_TO_STORE); and to getItem it need to be parsed to a javascript object, this means doing something like
const jsonValue = await AsyncStorage.getItem('NAME_OF_YOUR_KEY');
const value = JSON.parse(jsonValue);

React Native Flat List doesn't call onEndReached handler after two successful calls

I implement a very simple list that calls a server that returns a page containing books.Each book has a title, author, id, numberOfPages, and price). I use a Flat List in order to have infinite scrolling and it does its job very well two times in a row (it loads the first three pages) but later it doesn't trigger the handler anymore.
Initially it worked very well by fetching all available pages, but it stopped working properly after I added that extra check in local storage. If a page is available in local storage and it has been there no longer than 5 seconds I don't fetch the data from the server, instead I use the page that is cached. Of course, if there is no available page or it is too old I fetch it from the server and after I save it in local storage.(Something went wrong after adding this behavior related to local storage.)
Here is my component:
export class BooksList extends Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 0
};
}
async storePage(page, currentTime) {
try {
page.currentTime = currentTime;
await AsyncStorage.setItem(`page${page.page}`, JSON.stringify(page));
} catch (error) {
console.log(error);
}
}
subscribeToStore = () => {
const { store } = this.props;
this.unsubsribe = store.subscribe(() => {
try {
const { isLoading, page, issue } = store.getState().books;
if (!issue && !isLoading && page) {
this.setState({
isLoading,
books: (this.state.books ?
this.state.books.concat(page.content) :
page.content),
issue
}, () => this.storePage(page, new Date()));
}
} catch (error) {
console.log(error);
}
});
}
componentDidMount() {
this.subscribeToStore();
// this.getBooks();
this.loadNextPage();
}
componentWillUnmount() {
this.unsubsribe();
}
loadNextPage = () => {
this.setState({ pageNumber: this.state.pageNumber + 1 },
async () => {
let localPage = await AsyncStorage.getItem(`page${this.state.pageNumber}`);
let pageParsed = JSON.parse(localPage);
if (localPage && (new Date().getTime() - localPage.currentTime) < 5000) {
this.setState({
books: (
this.state.books ?
this.state.books.concat(pageParsed.content) :
page.content),
isLoading: false,
issue: null
});
} else {
const { token, store } = this.props;
store.dispatch(fetchBooks(token, this.state.pageNumber));
}
});
}
render() {
const { isLoading, issue, books } = this.state;
return (
<View style={{ flex: 1 }}>
<ActivityIndicator animating={isLoading} size='large' />
{issue && <Text>issue</Text>}
{books && <FlatList
data={books}
keyExtractor={book => book.id.toString()}
renderItem={this.renderItem}
renderItem={({ item }) => (
<BookView key={item.id} title={item.title} author={item.author}
pagesNumber={item.pagesNumber} />
)}
onEndReachedThreshold={0}
onEndReached={this.loadNextPage}
/>}
</View>
)
}
}
In the beginning the pageNumber available in the state of the component is 0, so the first time when I load the first page from the server it will be incremented before the rest call.
And here is the action fetchBooks(token, pageNumber):
export const fetchBooks = (token, pageNumber) => dispatch => {
dispatch({ type: LOAD_STARTED });
fetch(`${httpApiUrl}/books?pageNumber=${pageNumber}`, {
headers: {
'Authorization': token
}
})
.then(page => page.json())
.then(pageJson => dispatch({ type: LOAD_SUCCEDED, payload: pageJson }))
.catch(issue => dispatch({ type: LOAD_FAILED, issue }));
}
Thank you!

react-native-camera barcode scanner freezes, because it scans too fast

I am trying to use the barcode scanner from react-native-camera. First, off it scans a QR-code and extracts a String, after that it navigates to the next Screen with react-navigation. In the second screen, it makes an API-call.
Now if I go back to the scanner screen, de QR-code will be scanned immediately. That's where I run into an error and the scanner freezes. I usually get this error:
Can't call setState (or forceUpdate) on an unmounted component
I think it's because my componentWillUnmount cleanup doesn't work properly or fast enough, but I already cancel the axios request.
requestCode = (code) => {
if (cancel != undefined) {
cancel();
}
axios.get(API_URI + code, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
}).then(response => {
console.log(response)
//checks if code was already called
this.checkUsed(response.data)
})
.catch(error => {
this.setState({ isValid: false })
});
}
componentWillUnmount() {
cancel();
}
Maybe I could mount the camera-scanner a little bit later so it doesn't scan this fast or is it maybe even an error with React Navigation?
You can use a flag to control.
class QR extends Component {
constructor(props) {
super(props)
this.state = {
scanable: true
}
this.cameraAttrs = {
ref: ref => {
this.camera = ref
},
style: styles.preview,
type: RNCamera.Constants.Type.back,
barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
onBarCodeRead: ({ data }) => {
this.callback(data)
}
}
}
componentWillMount() {
this._mounted = true
}
componentWillUnmount() {
this._mounted = false
}
callback(text) {
if (!this.state.scanable) {
return
}
console.log(text)
this.setState({ scanable: false })
setTimeout(() => {
if (this._mounted) {
this.setState({ scanable: true })
}
}, 1000) // 1s cooldown
}
render() {
return (
<View style={styles.container}>
<RNCamera
{...this.cameraAttrs}
>
</RNCamera>
</View>
)
}
}