Component `props.data` Doesn't Reload After Apollo Refetch() - react-native

Following Apollo's Recompose Patterns
https://www.apollographql.com/docs/react/recipes/recompose.html
I've created a simple ErrorScreen component which outputs the error.message and displays a retry button.
const ErrorScreen = ({ refetch, data }) => {
const handleRetry = () => {
refetch()
.then(({ data, loading, error, networkStatus }) =>
// SUCCESS: now what do I do with the result?
console.log('DBUG:refetch', { networkStatus, loading, error })
)
.catch(error => console.log({ error }));
};
return (
<View style={styles.container}>
<Text>{(data && data.error && data.error.message) || 'Something went wrong'}</Text>
<Button title="Retry" onPress={handleRetry} />
</View>
);
};
The component the ErrorScreen is being called from is pretty straight forward. Here's an example of a it's usage, just in case the context helps...
import React from 'react';
import { FlatList, View } from 'react-native';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { compose } from 'recompose';
import ErrorScreen, { renderIfError, setRefetchProp } from './ErrorScreen';
import LoadingScreen, { renderWhileLoading } from './LoadingScreen';
import Card from '../components/Card';
const EventList = ({ navigation, data: { me, error } }) => {
return (
<View style={styles.container}>
<FlatList
data={me.teams}
renderItem={({ item }) => <CardItem team={item} navigation={navigation} />}
keyExtractor={team => team.id}
/>
</View>
);
};
const options = {
fetchPolicy: 'cache-and-network',
};
const withData = graphql(userEvents, options);
export default compose(
withData,
renderWhileLoading(LoadingScreen),
setRefetchProp(),
renderIfError(ErrorScreen)
)(EventList);
Expected Result
I had hoped that calling refetch() would...
Cause the ErrorScreen disappear, being replaced by the LoadingScreen
If refetch were successful, automatically load the component that orignally errored with the new data
If refetch failed, the ErrorScreen would appear again
Actual Result
This is what I've witnessed
ErrorScreen persists and does not disappear
Original props.data.error is unchanged and still shows original error, w/o query result
Original props.data.netWorkStatus is still 8, indicating an error. The networkStatus Docs seem to indicate that the status should change to 4 - refetching but maybe I'm looking in the wrong place.
Original props.data.loading never changed, which I guess is expected behavior since from what I've read this only indicates first query attempt
My Question
How do I accomplish the expected behavior documented above? What am I missing?
Related Issues
https://github.com/apollographql/apollo-client/issues/1622

I found an workaround, check it out in my own question:
Apollo refetch not rerendering component
just under the text 'Update'

Related

react native restart application

I created a barcode scanner App using expo-barcode-scanner.
I have some problems.
The purpose of the scanner is to get the barcode number and send it to barcode.monster and get product details. It works, but I have two main problems which I dont know what should I look for and how to resolve.
After the scanner get a barcode, I want to send to a confirmation screen, where the User should add the product into a category.
const handleBarCodeScanned = ({ type, data }) => {
reqForProduct(data);
setScanned(true);
setText(data);
navigation.navigate('Confirmation');
};
The function above is executed when the barcode camera find a number.
const reqForProduct = async barcode => {
try {
const Product = await axios.get(`https://barcode.monster/api/${barcode}`);
console.log(Product.data);
} catch (error) {
console.log(error);
}
}
The function above is responsible to get the product data.
THE NAVIGATION WORKS, BUT IF I PRESS THE BACK BUTTON AFTER THE FUNCTION SEND ME TO THE CONFIRMATION SCREEN, I CANNOT RESCAN OTHER BARCODE UNLESS I PRESS R (RELOAD) IN THE CONSOLE... THIS IS MY FIRST PROBLEM. Moreover, after coming back to the screen, the console is stucked with the last product fetched from the api.
The second problem is is to transfer the data fetched to the confirmation screen. I tried with the navigation prop like navigation.navigate('Confirmation', {fetchedDataObj} but is not working....
<Stack.Screen
name='Confirmation'
component={AddToContainerScreen} />
THE FULL PAGE CODE BELLOW ----------------------------------------------------
import {View, Text, Button, StyleSheet} from 'react-native';
import {useState, useEffect} from 'react';
import { BarCodeScanner } from 'expo-barcode-scanner';
import axios from 'axios';
const Scanner = ({navigation}) => {
const [permission, setPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const [text, setText] = useState('');
const permissionCamera = () => {
( async () => {
const {status} = await BarCodeScanner.requestPermissionsAsync();
setPermission(status == 'granted');
})()
}
const reqForProduct = async barcode => {
try {
const Product = await axios.get(`https://barcode.monster/api/${barcode}`);
console.log(Product.data);
} catch (error) {
console.log(error);
}
}
// Execute permission
useEffect(() => {
permissionCamera();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
reqForProduct(data);
setScanned(true);
setText(data);
navigation.navigate('Confirmation');
};
if (!permission) {
return (
<View>
<Text>Requesting camera permission</Text>
</View>
)
}
return (
<View style={styles.wrapper}>
<BarCodeScanner
style={StyleSheet.absoluteFill}
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
/>
</View>
)
};
const styles = StyleSheet.create({
wrapper: {
flex: 1
}
})
export default Scanner;
Can someone please help me?
BTW THE PRODUCT DATA FROM tHE API COMES SLOWeR THAN the APP MOVES TO THE CONFIRMATION SCREEN...
Problem 1: I think you need to reinitialize it on a focus even listener or something.
useEffect(() => {
permissionCamera();
}, []);
since useEffect() is basically a componentOnMount and it only fires the first time you load the page. When you navigate back this is not gonna fire. Please check if this is the case. You can do a simple log to confirm this.
For the 2nd problem, I can't help you much since there is only very little data. If you really need help, you could dm me on skype. I'll be glad to help you out.

React Native: data not displaying after async fetch

I'm developing a mobile app with React Native and Expo managed workflow. The app is supposed to serve as a song book with lyrics to songs and hymns. All of the lyrics are stored in Firebase' Firestore database and clients can load them in app. I started to implement offline functionality, where all of the lyrics are stored on the user's device using community's AsyncStorage.
I want to get the data stored in AsyncStorage first, set them to state variable holding songs and then look if user has internet access. If yes, I want to check for updates in Firestore, if there were any, I will set the data from Firestore to state variable holding songs. If user does not have internet access, the data from AsyncStorage will already be set to state variable holding songs.
I'm trying to achieve this with an async function inside useEffect hook with empty array of vars/dependecies. The problem I'm having is that no songs are rendered on screen even though they are successfuly retrieved from AsyncStorage.
(When I console.log the output of retrieving the data from AsyncStorage I can see all songs, when I console log songs or allSongs state var, I'm getting undefined)
Here is my simplified code:
import React, { useEffect, useState } from 'react';
import {
StyleSheet,
FlatList,
SafeAreaView,
LogBox,
View,
Text,
} from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { filter, _ } from 'lodash';
import { doc, getDoc } from 'firebase/firestore';
import NetInfo from '#react-native-community/netinfo';
import { db } from '../../../firebase-config';
import { ThemeContext } from '../../util/ThemeManager';
import {
getStoredData,
getStoredObjectData,
storeData,
storeObjectData,
} from '../../util/LocalStorage';
const SongsList = ({ route, navigation }) => {
// const allSongs = props.route.params.data;
const { theme } = React.useContext(ThemeContext);
const [loading, setLoading] = useState(false);
const [allSongs, setAllSongs] = useState();
const [songs, setSongs] = useState(allSongs);
const hymnsRef = doc(db, 'index/hymns');
useEffect(() => {
const setup = async () => {
setLoading(true);
const locData = await getStoredObjectData('hymnsData');
console.log(locData);
setAllSongs(locData);
setSongs(locData);
const netInfo = await NetInfo.fetch();
if (netInfo.isInternetReachable) {
const data = await getDoc(hymnsRef);
const lastChangeDb = data.get('lastChange').valueOf();
const hymnsData = data.get('all');
const lastChangeLocal = await getStoredData('lastChange');
if (lastChangeLocal) {
if (lastChangeLocal !== lastChangeDb) {
await storeData('lastChange', lastChangeDb);
await storeObjectData('hymnsData', hymnsData);
setAllSongs(hymnsData);
setSongs(hymnsData);
}
}
}
sortHymns();
setLoading(false);
};
setup();
}, []);
return (
<SafeAreaView style={[styles.container, styles[`container${theme}`]]}>
{!loading ? (
<FlatList
data={songs}
keyExtractor={(item) => item?.number}
renderItem={({ item }) => {
return <ListItem item={item} onPress={() => goToSong(item)} />;
}}
ItemSeparatorComponent={Separator}
ListHeaderComponent={
route.params.filters ? (
<SearchFilterBar
filters={filters}
handleFilter={handleFilter}
query={query}
handleSearch={handleSearch}
seasonQuery={seasonQuery}
setSeasonQuery={setSeasonQuery}
/>
) : (
<SearchBar handleSearch={handleSearch} query={query} />
)
}
/>
) : (
<View>
<Text>loading</Text>
</View>
)}
<StatusBar style={theme === 'dark' ? 'light' : 'dark'} />
</SafeAreaView>
);
};
export default SongsList;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The functions getStoredData, getStoredObjectData, storeData and storeObjectData are just getItem and setItem methods of AsyncStorage.
Here is my full code - GitHub.
What am I doing wrong? I've went over many tutorials and articles and it should be working... but I guess not?
Can you check if hymnsData is undefined? After the const hymnsData = data.get('all'); line.
If so, that would explain the issue - you are correctly setting the locData but then overwriting it immediately after. If that is the case, I would add hymnsData to the if condition if (hymnsData && lastChangeLocal) { ... }
If you log songs and allSongs right before the return (, do you see ever see that they are populated, briefly?
Another thing I'd do to debug, is comment out the
setAllSongs(hymnsData);
setSongs(hymnsData);
lines and see if it is working as expected with locData only
The problem was with the sortHymns() method. I moved it from it's own method to the useEffect and it's working now.

React Native GraphQL nested data array returning error

I have tried everything I can think of to solve this and am still stumped. I am using AWS AppSync GraphQL to store a dataset that I would like to call into a SectionList.
For the SectionList I am using a hardcoded id to call the data set through a GraphQL query. The SectionList displays correctly when I am using dummy data. It also displays the 1-to-1 relationships in the API correctly.
I already configured amplify to increase the statement depth and I can see the data in the Object.
Code for the SectionList
import React, { useState, useEffect } from 'react';
import { View, StyleSheet, Text, Image, ImageBackground, ScrollView, TouchableOpacity, SectionList, SafeAreaView } from 'react-native';
import Feather from 'react-native-vector-icons/Feather';
import AntDesign from 'react-native-vector-icons/AntDesign';
import { API, graphqlOperation } from 'aws-amplify';
import { getGame, listGameSections, listGames } from '../graphql/queries';
const Item = ({ title }) => (
<View>
<Text>
{title}
</Text>
</View>
);
const GameScreen = ({ navigation }) => {
const [game, setGame] = useState([]);
useEffect(() => {
const fetchGame = async () => {
const gameInfo = { id: '0e2cb273-b535-4cf7-ab16-198c44a4991c'};
if (!gameInfo) {
return;
}
try {
const response = await API.graphql(graphqlOperation(getGame, {id: gameInfo.id}))
setGame(response.data.getGame);
console.log(response);
} catch (e) {
}
};
fetchGame();
}, [])
return (
<SafeAreaView>
<View>
<Text>
{game.name}
</Text>
</View>
<SectionList
sections={game.sections.items}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Item title={item} />}
renderSectionHeader={({ section: { title } }) => (
<View>
<Text>{title}</Text>
</View>
)}>
</SafeAreaView>
)
};
export default GameScreen;
Log of the object.
I am attempting to display the getGame.sections.items array but am returning an error undefined is not an object. Cannot read property items of undefined.
Please help, I am so stumped now. When I call game.name earlier in the function it displays correctly, but game.sections.items throws an error in the SectionList that it is undefined.
Xadm, you pointed me in the right direction. I added this to my code:
const [game, setGame] = useState({});
const [gameSection, setGameSection] = useState([]);
and in my useEffect:
setGameSection(response.data.getGame.sections.items)
When calling the data, game.name wanted an object, while game.sections.items wanted an array for the SectionList. Adding 2 different functions for each initial states, one for the objects and one for the array, was able to fix the problem and render the data.

test content of a Text element in a stateful component

I am using react-native-testing-library. My component is quite simple:
import React, {Component} from 'react';
import {Text, View} from 'react-native';
import {information} from './core/information';
export default class Logo extends Component {
constructor() {
super();
this.state = {
name: ''
};
information()
.then((details) => {
this.setState({
name: details['name']
});
})
.catch((e) => {
console.log(e);
});
}
render() {
return (
<>
<View>
<Text>{this.state.name}</Text>
</View>
</>
);
}
}
I want to make sure contains the right content. I tried the following but it is failing:
import * as info from "./lib/information";
it('displays correct text', () => {
const spy = jest.spyOn(info, 'information')
const data = {'name':'name'}
spy.mockResolvedValue(Promise.resolve(data));
const {queryByText, debug} = render(<Logo />);
expect(queryByText(data.name)).not.toBeNull();
expect(spy).toHaveBeenCalled();
});
I can confirm the function information() was spied on correctly but still debug(Logo) shows the Text element with empty string.
If it's correctly spying you can try this. I encourage you to use the testID props for the components
render() {
return (
<>
<View>
<Text testID="logo-text">{this.state.name}</Text>
</View>
</>
);
}
import * as info from "./lib/information";
import { waitForElement, render } from "react-native-testing-library";
it('displays correct text', () => {
const spy = jest.spyOn(info, 'information')
const data = {'name':'name'}
//this is already resolving the value, no need for the promise
spy.mockResolvedValue(data);
const {getByTestId, debug} = render(<Logo />);
//You better wait for the spy being called first and then checking
expect(spy).toHaveBeenCalled();
//Spy function involves a state update, wait for it to be updated
await waitForElement(() => getByTestId("logo-text"));
expect(getByTestId("logo-text").props.children).toEqual(data.name);
});
Also, you should move your information call inside a componentDidMount

mount a component only once and not unmount it again

Perhaps what I think can solve my issue is not the right one. Happy to hearing ideas. I am getting:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and async task in a useEffect cleanup function
and tracked it down to one component that is in my headerRight portion of the status bar. I was under the impression it mounts only once. Regardless, the component talks to a syncing process that happens and updates the state. For each status of the sycing, a different icon is displayed.
dataOperations is a NativeModules class that talks to some JAVA that does the background syncing and sends the status to RN.
import React, {useState, useEffect} from 'react';
import {DeviceEventEmitter } from 'react-native';
import DataOperations from "../../../../lib/databaseOperations"
const CommStatus: () => React$Node = () => {
let [status, updateStatus] = useState('');
const db = new DataOperations();
const onCommStatus = (event) => {
status = event['status'];
updateStatus(status);
};
const startSyncing = () => {
db.startSyncing();
};
const listner = DeviceEventEmitter.addListener(
'syncStatusChanged',
onCommStatus,
);
//NOT SURE THIS AS AN EFFECT
const removeListner = () =>{
DeviceEventEmitter.removeListener(listner)
}
//REMOVING THIS useEffect hides the error
useEffect(() => {
startSyncing();
return ()=>removeListner(); // just added this to try
}, []);
//TODO: find icons for stopped and idle. And perhaps animate BUSY?
const renderIcon = (status) => {
//STOPPED and IDLE are same here.
if (status == 'BUSY') {
return (
<Icon
name="trending-down"
/>
);
} else if (status == 'IS_CONNECTING') {
...another icon
}
};
renderIcon();
return <>{renderIcon(status)}</>;
};
export default CommStatus;
The component is loaded as part of the stack navigation as follows:
headerRight: () => (
<>
<CommStatus/>
</>
),
you can use App.js for that.
<Provider store={store}>
<ParentView>
<View style={{ flex: 1 }}>
<AppNavigator />
<AppToast />
</View>
</ParentView>
</Provider>
so in this case will mount only once.