ReferenceError: Can't find variable: props - react-native

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.

Related

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.

Get the position and size of a dialog component in react native

I am using a Dialog component from 'react-native-paper' in a simple functional component like:
...
const myScreen = () => {
useEffect(() => {
Keyboard.addListener('keyboardDidShow', frames => {
// get the Dialog component measures
});
);
return (
<View>
...
<Portal>
<Dialog>
...
</Dialog>
</Portal>
</View>
);
}
I need to get the size and position of the dialog on screen in the useEffect.
I have tried every suggested solution and nothing seems to work.

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;

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

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'