Unable to display GraphQL data using Apollo Client in ReactNative - react-native

I am new to RN development.
I am trying to setup Apollo client to fetch GraphQL data.
I am not able to get the data back.
Here is the code in App.js
import {ApolloProvider} from '#apollo/client';
import AppNavigator from './app/navigation/AppNavigator';
import apiClient from './app/api/client';
function App(props) {
console.log('From Main Component');
return (
<ApolloProvider client={apiClient}>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</ApolloProvider>
);
}
In AppNavigator, Im displaying only the home screen for now.
In ./app/api/client.js
import {ApolloClient, InMemoryCache} from '#apollo/client';
const apiClient = new ApolloClient({
uri: 'https://phccapi-dev.azure-api.net/NaraakomGraphQLGateway/graphql',
cache: new InMemoryCache(),
headers: {
authorization:'Bearer xxxxxxxxxxxxxx',
'Subscription-Key': 'xxxxxxxxxxxxxxxxxx',
},
});
export default apiClient;
In api/queries.js
import {gql} from '#apollo/client';
export default {
GET_USER_NAME,
};
//Used in the Home Screen
const GET_USER_NAME = gql`
query {
userProfile {
fName
lName
}
}
`;
In HomeScreen.js
import DisplayData from '../components/DisplayData';
function HomeScreen({navigation}) {
return (
<View>
<Text> Home Screen text </Text>
<DisplayData/>
<Button title="Profile" onPress={() => navigation.navigate('Profile')} />
</View>
);
}
export default HomeScreen;
In DisplayData.js
import {useQuery} from '#apollo/client';
import GET_USER_NAME from './../api/queries';
function DisplayData(props) {
console.log('Logging query : ' + GET_USER_NAME);
const {loading, error, data} = useQuery(GET_USER_NAME);
if (loading) {
console.log('Loading : ' + loading);
return (
<View>
<Text>Loading...</Text>
</View>
);
}
if (error) {
console.log('Error : ' + error);
return (
<View>
<Text>Error :</Text>
</View>
);
}
console.log('Data : ' + data);
return (
<View>
<Text> In Component </Text>
<Text> {data.userProfile.fName} </Text>
</View>
);
}
export default DisplayData;
From GraphiQL, if I run the query I am getting the data.
If I follow the Apollo Client Documentation https://www.apollographql.com/docs/react/get-started/ and put all code in a single file , I am able to get the data
But most tutorials that I have gone through suggest to follow a hierarchy , a folder structure to properly maintain code and separate the concerns as the app grows
Hence I have tried to separate different things in different files and components
But I think I have not been able to set it up properly.
Before learning RN , I have also just learned JS and React
So I am guessing that there could also be some issue in the way I have declared constants/components , exported and imported them

Related

React-Native Redux connect syntaxError

I'm learning Redux. I don't konw why syntax error appear.
I'm building a simple counter app.
This is my code. I don't well react-native and redux. so I really need help.Plase help me.
I don't know where I'm wrong. I think problem is View.js.
App.js
`import React from 'react';
import Screen from './src/View';
import { Provider,createStore } from 'react-redux';
const store = createStore(reducers);
const App=()=>{
return(
<Provider store={store}>
<Screen/>
</Provider>
)
}
export default App;`
View.Js
This part have syntax error.
import React from "react";
import { StyleSheet,View, Text, Button } from "react-native";
import { connect } from 'react-redux';
export default function Screen (){
return(
<View >
<Text style={styles.containers}>counter: </Text>
<Text style={styles.containers}>{this.props.state.counterNum}</Text>
<Button title="+" >+</Button>
<Button title="-" >-</Button>
</View>
);
};
const styles=StyleSheet.create({
containers : {
textAlign:"center",
}
}
);
function mapStateToProps(state){
return {
state: state.counterNum
};
}
// Actions을 props로 변환
function matchDispatchToProps(dispatch){
return bindActionCreators({
counter : counterNum
}, dispatch);
}
//(error here)
export default connect(mapStateToProps, matchDispatchToProps)(Screen)
index.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
const initialState={
counter:[
{
counterNum:0,
},
],
};
const counter=( state= initialState, action)=>{
const {counter}=state;
switch(action.type){
case 'INCREMENT':
return({
counter:[
...counter.slice(0,action.index),
{
counterNun : counter[action.index].counterNum +1,
}
]
});
case 'DECREMENT' :
return({
counter:[
...counter.slice(0,action.index),
{
counterNun : counter[action.index].counterNum -1,
}
]
});
default :
return state;
}
}
export default counter;
Any help/knowledge will be appreciated thanks!
It looks like you are trying to use syntax for class components.
Try passing props into Screen like this:
export default function Screen (props){
return(
<View >
<Text style={styles.containers}>counter: </Text>
<Text style={styles.containers}>{props.state}</Text>
<Button title="+" >+</Button>
<Button title="-" >-</Button>
</View>
);
};
no need for this keyword
Also, would be better to do this:
function mapStateToProps(state){
return {
counterNum: state.counterNum // <-- change property name to counterNum
};
}
then you can access this value with props.counterNum

ReferenceError: Can't find variable: props

In React-Native, I am creating a functional component called ImageSelector, in which I am using expo-image-picker and using the image URI as a required field in a parent component using Formik. My simulator works and I am able to successfully pick a generic image and log the image URI in the console ('success: ' + result.uri) but here are the following errors:
I want to display the image in the image component below but the image does not display (it does not break). I get the following error Unhandled promise rejection: ReferenceError: Can't find variable: props which I suppose is referring to the parent form component but I do not know what to change to get this error to go away.
Parent Component
import { View, Text, Button } from 'react-native';
import { Formik } from 'formik';
import * as yup from 'yup';
import ImageSelector from '../shared/imagePicker';
const newPostSchema = yup.object({
image: yup.string()
.required(),
})
export default function CreatePost({navigation}) {
const setImageURI = (image) => {
props.setFieldValue('imageUri', image.uri)
}
return (
<View style={styles?.container}>
<Formik
initialValues={{
imageURI: null,
}}
validationSchema={newPostSchema}
onSubmit={(values, actions) => {
console.log(values);
navigation.navigate('ReviewPost', {
imageURI: values.imageURI,
});
}}
>
{props => (
<View>
<ImageSelector
image={props.values.imageURI}
onImagePicked={setImageURI}
/>
<Button onPress={props.handleSubmit} title='REVIEW' />
</View>
)}
</Formik>
</View>
)
}
*** Nested ImageSelector Component in another file ***
import React, {useState, useEffect} from 'react';
import {View, Button, Image, StyleSheet} from 'react-native';
import * as ImagePicker from 'expo-image-picker';
const ImageSelector = ({image, onImagePicked}) => {
const [selectedImage, setSelectedImage] = useState();
useEffect(() => {
if(image) {
console.log('useEffect: ' + image);
setSelectedImage({uri: image});
}
}, [image])
pickImageHandler = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
title: 'Choose Image',
maxWidth: 800,
maxHeight: 600,
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1
});
if (!result.cancelled) {
console.log('success: ' + result.uri);
onImagePicked({uri: result.uri});
console.log('a');
setSelectedImage({uri: result.uri});
console.log('b');
}
if (result.cancelled) {
console.log('result cancelled: ' + result.cancelled);
}
}
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
<Image source={selectedImage} />
</View>
<View style={styles.button}>
<Button title='Pick Image' onPress={this.pickImageHandler} />
</View>
</View>
)
}
The following 4 lines do not execute (console logs are for testing to ensure they don't get called):
onImagePicked({uri: result.uri});*
console.log('a'); *
setSelectedImage({uri: result.uri});*
console.log('b');*
I need to get the props-related error to go away, set selectedImage to equal result.uri, and have the image display in the <Image /> component using selectedImage.uri as the image source.
Help?
The problem here is in the error message. Since you are creating a functional component called CreatePost, the typical syntax for passing props would be
export default function CreatePost(props) {
...
}
So your component can access the props that are passed down to it, such as setFieldValue, however, you have used the spread operator {navigation} instead of props, so you are already extracting all the props when you do that. Thus, the scope does not know of any props variable. So, for now, I would try changing the argument to this
export default function CreatePost(props) {
const { navigation } = props;
...
}
That way wherever else in the scope you have referenced props will still work and you will not lose access to the navigation property either, alternatively, you can simply change 'navigation.navigate' to 'props.navigation.navigate' also. So javascript is saying cant find variable props, because to it, this is just a simple vanilla javascript function, and it does not intuitively know of a variable called props, you have to explicitly call it that.
Also, I feel like there might still be issues in this part of the code
{props => (
<View>
<ImageSelector
image={props.values.imageURI}
onImagePicked={setImageURI}
/>
<Button onPress={props.handleSubmit} title='REVIEW' />
</View>
)}
So it would help if you could post the code where you are using your component, to see what props, such as setFieldValue, navigation etc.you are passing.
You can just rewrite that part as
<Formik
initialValues={{
imageURI: null,
}}
validationSchema={newPostSchema}
onSubmit={(values, actions) => {
console.log(values);
navigation.navigate('ReviewPost', {
imageURI: values.imageURI,
});
}}
>
<View>
<ImageSelector
image={props.values.imageURI}
onImagePicked={setImageURI}
/>
<Button onPress={props.handleSubmit} title='REVIEW' />
</View>
Without doing the {props => part as with the refactor now you already have access to props in the scope.

How to restart app (react native and expo)

I use expo so I've no access to android folder.
I want to restart my app for first time. How can I do that?
I use react-native-restart, but not wroking and I have an error now:
null is not an object (evaluating 'x.default.restart;)
Codes:
componentDidMount() {
if (I18nManager.isRTL) {
I18nManager.forceRTL(false);
RNRestart.Restart();
}
}
How Can I restart my app?
I've had the same problem for over a month, nothing helped me, so I developed a library to accomplish this, simple install it using:
npm i fiction-expo-restart
and import it like:
import {Restart} from 'fiction-expo-restart';
and then when you want to perform a restart, use:
Restart();
Note in case this answer gets old, you can check the library here: https://www.npmjs.com/package/fiction-expo-restart
I have faced the same issue and found this solution somewhere.
You can try to use Updates from expo like this:
import { Updates } from 'expo';
Updates.reload();
import { StatusBar } from "expo-status-bar";
import React from "react";
import { Button, I18nManager, StyleSheet, Text, View } from "react-native";
import * as Updates from "expo-updates";
async function toggleRTL() {
await I18nManager.forceRTL(I18nManager.isRTL ? false : true);
await Updates.reloadAsync();
}
export default function App() {
return (
<View style={styles.container}>
<Text>{new Date().toString()}</Text>
<Text>{I18nManager.isRTL ? "RTL" : "LTR"}</Text>
<View style={{ marginVertical: 5 }} />
<Button title="Reload app" onPress={() => Updates.reloadAsync()} />
<View style={{ marginVertical: 5 }} />
<Button title="Toggle RTL" onPress={() => toggleRTL()} />
<StatusBar style="auto" />
</View>
);
}
https://github.com/brentvatne/updates-reload/blob/master/App.js
It's the only working way for me. When i try automatically reload app in useEffect - it crashes, so i make a separate screen where i ask user to press button to reload app
For Expo SDK 45+ please use
import * as Updates from "expo-updates"
Updates.reloadAsync()
The module fiction-expo-restart is not maintained anymore.
If you are using react-native-code-push library, you can restart with this;
import CodePush from 'react-native-code-push';
CodePush.restartApp();
What I did was to build a Restart component that is not a const but a var. And an applyReload() function that sets that var to an empty component <></> if the reload bool state is true, triggering the re-render.
The re-render will reinstate the Restart var back to its original structure, but a new instance is then created, effectively reloading everything that is inside the <Restart> tag:
My App.tsx:
export default function App() {
const [reload, setReload] = useState(false);
type Props = { children: ReactNode };
var Restart = ({ children }: Props) => {
return <>{children}</>;
};
const applyReload = () => {
if (reload) {
Restart = ({ children }: Props) => {
return <></>;
};
setReload(false);
}
};
useEffect(applyReload);
useEffect(() => {
// put some code here to modify your app..
// test reload after 6 seconds
setTimeout(() => {
setReload(true);
}, 6000);
}, []);
return (
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1 }}>
<PaperProvider theme={appTheme}>
<NavigationContainer theme={appTheme} documentTitle={{ enabled: false }}>
<AppContext.Provider value={appContext}>
<Restart>
<MyMainAppComponent />
</Restart>
</AppContext.Provider>
</NavigationContainer>
</PaperProvider>
</SafeAreaView>
</SafeAreaProvider>
);
I also added the 'setReload' state function to my '<AppContext.Provider>' so anywhere down my App it is possible to trigger the App reload.

Unstated store based React Navigation causing warning

I'm using react-navigation and Unstated in my react native project.
I have a situation where I would like use:
this.props.navigation.navigate("App")
after successfully signing in.
Problem is I don't want it done directly from a function assigned to a submit button. I want to navigate based upon a global Unstated store.
However, it means that I would need to use a conditional INSIDE of the Subscribe wrapper. That is what leads to the dreaded Warning: Cannot update during an existing state transition (such as within 'render').
render() {
const { username, password } = this.state;
return (
<Subscribe to={[MainStore]}>
{({ auth: { state, testLogin } }) => {
if (state.isAuthenticated) {
this.props.navigation.navigate("App");
return null;
}
console.log("rendering AuthScreen");
return (
<View style={styles.container}>
<TextInput
label="Username"
onChangeText={this.setUsername}
value={username}
style={styles.input}
/>
<TextInput
label="Password"
onChangeText={this.setPassword}
value={password}
style={styles.input}
/>
{state.error && (
<Text style={styles.error}>{state.error.message}</Text>
)}
<Button
onPress={() => testLogin({ username, password })}
color="#000"
style={styles.button}
>
Sign in!
</Button>
</View>
);
}}
</Subscribe>
);
It works. But what's the correct way to do it?
I don't have access to MainStore outside of Subscribe and therefore outside of render.
I'm not sure about the react-navigation patterns but you could use a wrapper around this component which subscribes to 'MainStore' and pass it down to this component as a prop. That way you'll have access to 'MainStore' outside the render method.
I have since found a better solution.
I created an HOC that I call now on any Component, functional or not, that requires access to the store. That give me access to the store's state and functions all in props. This means, I am free to use the component as it was intended, hooks and all.
Here's what it looks like:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import MainStore from "../store/Main";
const withUnstated = (
WrappedComponent,
Stores = [MainStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
// { ...v } to force the WrappedComponent to rerender
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Used like so in this Header example:
import React from "react";
import { Text, View } from "react-native";
import styles from "./styles";
import { states } from "../../services/data";
import withUnstated from "../../components/WithUnstated";
import MainStore from "../../store/Main";
const Header = ({
MainStore: {
state: { vehicle }
}
}) => (
<View style={styles.plateInfo}>
<Text style={styles.plateTop}>{vehicle.plate}</Text>
<Text style={styles.plateBottom}>{states[vehicle.state]}</Text>
</View>
);
export default withUnstated(Header, [MainStore]);
So now you don't need to create a million wrapper components for all the times you need your store available outside of your render function.
As, as an added goodie, the HOC accepts an array of stores making it completely plug and play. AND - it works with your navigationOptions!
Just remember to add displayName to your stores (ES-Lint prompts you to anyway).
This is what a simple store looks like:
import { Container } from "unstated";
class NotificationStore extends Container {
state = {
notifications: [],
showNotifications: false
};
displayName = "NotificationStore";
setState = payload => {
console.log("notification store payload: ", payload);
super.setState(payload);
};
setStateProps = payload => this.setState(payload);
}
export default NotificationStore;

Rails ActionCable and React Native

I'm working on a companion React Native app to accompany my RoR webapp, and want to build a chat feature using ActionCable (websockets). I cannot get my React Native app to talk to ActionCable.
I have tried a number of libraries including react-native-actioncable with no luck. The initial connection seems to be working (I know this because I was having errors before and they've since gone away when I passed the proper params).
This is an abbreviated version of my React Native code:
import ActionCable from 'react-native-actioncable'
class Secured extends Component {
componentWillMount () {
var url = 'https://x.herokuapp.com/cable/?authToken=' + this.props.token + '&client=' + this.props.client + '&uid=' + this.props.uid + '&expiry=' + this.props.expiry
const cable = ActionCable.createConsumer(url)
cable.subscriptions.create('inbox_channel_1', {
received: function (data) {
console.log(data)
}
})
}
render () {
return (
<View style={styles.container}>
<TabBarNavigation/>
</View>
)
}
}
const mapStateToProps = (state) => {
return {
email: state.auth.email,
org_id: state.auth.org_id,
token: state.auth.token,
client: state.auth.client,
uid: state.auth.uid,
expiry: state.auth.expiry
}
}
export default connect(mapStateToProps, { })(Secured)
Anyone with more experience connecting ActionCable to React Native and can help me out?
The url endpoint you're attaching to is not a websocket, so that's probably your issue. The example app they've listed was updated just 2 months ago and is based on RN 0.48.3, so I have to guess that it probably still works. Have you tried cloning and running it?
Looks like you'll also need to setup a provider as well (<ActionCableProvider>)
import RNActionCable from 'react-native-actioncable';
import ActionCableProvider, { ActionCable } from 'react-actioncable-provider';
const cable = RNActionCable.createConsumer('ws://localhost:3000/cable');
class App extends Component {
state = {
messages: []
}
onReceived = (data) => {
this.setState({
messages: [
data.message,
...this.state.messages
]
})
}
render() {
return (
<View style={styles.container}>
<ActionCable channel={{channel: 'MessageChannel'}} onReceived={this.onReceived} />
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<View>
<Text>There are {this.state.messages.length} messages.</Text>
</View>
{this.state.messages.map((message, index) =>
<View key={index} style={styles.message}>
<Text style={styles.instructions}>
{message}
</Text>
</View>
)}
</View>
)
}
}
export default class TestRNActionCable extends Component {
render() {
return (
<ActionCableProvider cable={cable}>
<App />
</ActionCableProvider>
);
}
}