How to call a function from another .js file in React Native Expo - javascript

I'm new to React and I've faced such a problem:
I have Main.js file with a button:
import * as React from 'react';
import { StyleSheet, Text, View, SafeAreaView, Pressable, Alert } from 'react-native';
import { MaterialIcons, MaterialCommunityIcons} from '#expo/vector-icons';
import { DrawerActions } from '#react-navigation/native';
import Scanner from './Scanner'
export default function Main({ navigation }) {
return (
....
<View style={styles.container}>
<Pressable style={styles.button} onPress={ <Scanner() function call> }>
<Text style={styles.buttonText}>Take charger</Text>
</Pressable>
</View>
);
}
An I have another file Scanner.js:
import React, { useState, useEffect } from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
export default function Scanner() {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
alert(`Bar code with type ${type} and data ${data} has been scanned!`);
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View style={styles.container}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
{scanned && <Button title={'Tap to Scan Again'} onPress={() => setScanned(false)} />}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
}
});
How can I call a function Scanner() from Scanner.js on button click in the Main()
function in Main.js. I've tried onPress={() => Scanner} but it didn't work for me because of wrong hooks usage.

You should write function Scan in the parent component -> Main.js and forward it to the children component -> Scanner.js. That's called prop drilling
https://blogs.perficient.com/2021/12/03/understanding-react-context-and-property-prop-drilling/#:~:text=Prop%20drilling%20refers%20to%20the,because%20of%20its%20repetitive%20code.

This is not a good patern, but you can do that. You can create the function in the parent and send it as a prop to the child or you can use context hook from react, that hook lets you pass the function from the child to the parent. Another method is to use forwardRef, this will let you call the child function from the parent.

Related

How to display all images from firebase storage on React Native app?

I can upload pictures to FB storage. But now I'm trying to display them all on React Native app live.
For some reason, I can't make it work. There are not lots of videos on youtube or recent tutorials on how to do this. I'm trying to make it work by looking it up on Stackoverflow from people who had some similar problems, but no luck so far.
Here's my app code
import { StyleSheet, View } from 'react-native';
import Uploadscreen from './src/UploadSreen';
import ListPictures from './src/ListPictures';
export default function App() {
return (
<View style={styles.container}>
<Uploadscreen/>
<ListPictures/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
The UploadScreen component works totally fine (this is the one uploading to FB)
And here's my separate component for looping all the images in firebase storage(Which I need help with).
import { firebase } from '../config'
import { View, Image } from 'react-native';
import React, { useState } from 'react';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import 'firebase/compat/storage';
const ListPictures = () => {
const [sampleImage, setSampleImage] = useState();
const getSampleImage = async () => {
const imageRefs = await firebase.storage().ref().child('Front/').listAll();
const urls = await Promise.all(imageRefs.items.map((ref) => ref.getDownloadURL()));
setSampleImage(urls);
}
{ sampleImage && getSampleImage().map(url => (
<View style={{ justifyContent: 'center' }} key={imageRef.id}>
<Image source={{ uri: url }} style={{ width: 350, height: 350 }} />
</View>
))}
}
export default ListPictures;
Any help would be much appreciated!
Try this
import { firebase } from '../config'
import { View, Image } from 'react-native';
import React, { useState, useEffect } from 'react';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import 'firebase/compat/storage';
const ListPictures = () => {
const [sampleImage, setSampleImage] = useState([]);
const getSampleImage = async () => {
const imageRefs = await firebase.storage().ref().child('Front/').listAll();
const urls = await Promise.all(imageRefs.items.map((ref) => ref.getDownloadURL()));
setSampleImage(urls);
}
useEffect(()=>{
getSampleImage()
},[])
{ sampleImage.length!=0 && sampleImage.map(url => (
<View style={{ justifyContent: 'center' }} key={imageRef.id}>
<Image source={{ uri: url }} style={{ width: 350, height: 350 }} />
</View>
))}
}
export default ListPictures;
You shouldn't call asynchronous code while building the UI output as you do here:
{ sampleImage && getSampleImage().map(url => (
What you have will be called on every render, which is likely not what you want.
Instead, put such a call in a useEffect hook with an empty dependencies array:
useEffect(() => {
getSampleImage();
}, []);
This way the call to getSampleImage() runs when the component gets created, rather than on each render.

React Native not recognizing useState hook

Render Error:
(0,_reactNative.usestate) is not a function.(In'(0,_reactNative.useState)(''),'(0,_reactNative.useState)'is undefined
This is the error my code is producing. All the imports are up to date. Not sure why it is not recognizing useState.
import React from 'react';
import { View, StyleSheet,TextInput,useEffect,useState } from 'react-native';
import db from 'D:/App development/PRLog/PrLogBeta/database/firestore'
const EnteryModal = props => {
const [numReps,setNumReps] = useState('');
useEffect(() => {
db.collection('Bench').add({reps:{newNum}})
},[]).then(() => {
console.log('Number Added!');
});
return(
<View style={styles.inputStyle}>
<form>
<TextInput
style = {styles.inputStyle}
keyboardType='number-pad'
placeholder={props.RepsOrWeight}
placeholderTextColor = 'white'
textAlign='center'
onChangeText={newNum => setNumReps(newNum)}
defaultValue={numReps}
onSubmitEditing={useEffect(() => {
db.collection('Bench').add({reps:{newNum}})
},[]).then(() => {
console.log('Number Added!');
})}
>
</TextInput>
</form>
</View>
);
};
You need to import useState and useEffect from React, not React Native
You cannot call .then() on useEffect since it does not return a promise.
You can't use useEffect as a callback function.
EDIT: Code example:
Based on the snippet from your question, it seems like you're trying to trigger a POST request to your database on submitting the text input. This can be achieved without useEffect by simply passing a handler function to your text input like so.
import React, { useEffect, useState } from 'react';
import { View, StyleSheet,TextInput } from 'react-native';
import db from 'D:/App development/PRLog/PrLogBeta/database/firestore'
const EnteryModal = props => {
const [numReps,setNumReps] = useState('');
const handleSubmit = async () => {
try {
await db.collection('Bench').add({reps:{newNum}});
console.log('Number Added!');
} catch (error) {
console.log(error)
}
}
return(
<View style={styles.inputStyle}>
<form>
<TextInput
style = {styles.inputStyle}
keyboardType='number-pad'
placeholder={props.RepsOrWeight}
placeholderTextColor = 'white'
textAlign='center'
onChangeText={newNum => setNumReps(newNum)}
defaultValue={numReps}
onSubmitEditing={handleSubmit}
>
</TextInput>
</form>
</View>
);
};
Use useState and useEffect as a react component not as react native component.
as shown in below example.
import React, { useEffect, useState } from 'react';
import { View, StyleSheet, TextInput} from 'react-native';
import db from 'D:/App development/PRLog/PrLogBeta/database/firestore'
const EnteryModal = props => {
const [numReps, setNumReps] = useState('');
useEffect(() => {
dataBaseCollection();
console.log("Number Added!");
}, []);
const dataBaseCollection = () => {
db.collection('Bench').add({ reps: { newNum } });
}
return (
<View style={styles.inputStyle}>
<form>
<TextInput
style={styles.inputStyle}
keyboardType='number-pad'
placeholder={props.RepsOrWeight}
placeholderTextColor='white'
textAlign='center'
onChangeText={newNum => setNumReps(newNum)}
defaultValue={numReps}
onSubmitEditing={(event) => {
dataBaseCollection();
}}
>
</TextInput>
</form>
</View>
);
};

React Native: Passing useState() data to unrelated screens

Explanation: I am creating a fitness app, my fitness app has a component called WorkoutTimer that connects to the workout screen, and that screen is accessed via the HomeScreen. Inside the WorkoutTimer, I have an exerciseCount useState() that counts every time the timer does a complete loop (onto the next exercise). I have a different screen called StatsScreen which is accessed via the HomeScreen tab that I plan to display (and save) the number of exercises completed.
What I've done: I have quite literally spent all day researching around this, but it seems a bit harder with unrelated screens. I saw I might have to use useContext() but it seemed super difficult. I am fairly new to react native so I am trying my best haha! I have attached the code for each screen I think is needed, and attached a screenshot of my homeScreen tab so you can get a feel of how my application works.
WorkoutTimer.js
import React, { useState, useEffect, useRef } from "react";
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Button,
Animated,
Image,
SafeAreaView,
} from "react-native";
import { CountdownCircleTimer } from "react-native-countdown-circle-timer";
import { Colors } from "../colors/Colors";
export default function WorkoutTimer() {
const [count, setCount] = useState(1);
const [exerciseCount, setExerciseCount] = useState(0);
const [workoutCount, setWorkoutCount] = useState(0);
const exercise = new Array(21);
exercise[1] = require("../assets/FR1.png");
exercise[2] = require("../assets/FR2.png");
exercise[3] = require("../assets/FR3.png");
exercise[4] = require("../assets/FR4.png");
exercise[5] = require("../assets/FR5.png");
exercise[6] = require("../assets/FR6.png");
exercise[7] = require("../assets/FR7.png");
exercise[8] = require("../assets/FR8.png");
exercise[9] = require("../assets/S1.png");
exercise[10] = require("../assets/S2.png");
exercise[11] = require("../assets/S3.png");
exercise[12] = require("../assets/S4.png");
exercise[13] = require("../assets/S5.png");
exercise[14] = require("../assets/S6.png");
exercise[15] = require("../assets/S7.png");
exercise[16] = require("../assets/S8.png");
exercise[17] = require("../assets/S9.png");
exercise[18] = require("../assets/S10.png");
exercise[19] = require("../assets/S11.png");
exercise[20] = require("../assets/S12.png");
exercise[21] = require("../assets/S13.png");
return (
<View style={styles.container}>
<View style={styles.timerCont}>
<CountdownCircleTimer
isPlaying
duration={45}
size={240}
colors={"#7B4FFF"}
onComplete={() => {
setCount((prevState) => prevState + 1);
setExerciseCount((prevState) => prevState + 1);
if (count == 21) {
return [false, 0];
}
return [(true, 1000)]; // repeat animation for one second
}}
>
{({ remainingTime, animatedColor }) => (
<View>
<Image
source={exercise[count]}
style={{
width: 150,
height: 150,
}}
/>
<View style={styles.timeOutside}>
<Animated.Text
style={{
color: animatedColor,
fontSize: 18,
position: "absolute",
marginTop: 67,
marginLeft: 35,
}}
>
{remainingTime}
</Animated.Text>
<Text style={styles.value}>seconds</Text>
</View>
</View>
)}
</CountdownCircleTimer>
</View>
</View>
);
}
const styles = StyleSheet.create({})
WorkoutScreen.js
import React, { useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import WorkoutTimer from "../components/WorkoutTimer";
export default function WorkoutScreen() {
return (
<View style={styles.container}>
<WorkoutTimer />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
HomeScreen.js
import React from "react";
import { StyleSheet, Text, View, SafeAreaView, Button } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { AntDesign } from "#expo/vector-icons";
import { Colors } from "../colors/Colors";
export default function HomeScreen({ navigation }) {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.pageRef}>SUMMARY</Text>
<Text style={styles.heading}>STRETCH & ROLL</Text>
<View style={styles.content}>
<TouchableOpacity
style={styles.timerDefault}
onPress={() => navigation.navigate("WorkoutScreen")}
>
<Button title="START WORKOUT" color={Colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={styles.statContainer}
onPress={() => navigation.navigate("StatsScreen")}
>
<AntDesign name="barschart" size={18} color={Colors.primary} />
<Text style={{ color: Colors.primary }}>Statistics</Text>
<AntDesign name="book" size={18} color={Colors.primary} />
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({})
StatsScreen.js
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
export default function StatsScreen() {
return (
<View style={styles.container}>
<Text display={exerciseCount} style={styles.exerciseText}>
{exerciseCount}
</Text>
<Text display={workoutCount} style={styles.workoutText}>
{workoutCount}
</Text>
</View>
);
}
const styles = StyleSheet.create({});
Home Screen Image
As far as I can tell, you're almost there! You're trying to get your 2 state
variables from the WorkoutTimer like this:
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
Unfortunatly this won't work :( . These two variables change throughout your
App's life-time and that kinda makes them "special".
In React, these kinds of variables need to be declared in a parent component
and passed along to all children, which are interested in them.
So in your current Setup you have a parent child relationship like:
HomeScreen -> WorkoutScreen -> WorkoutTimer.
If you move the variables to HomeScreen (HomeScreen.js)
export default function HomeScreen({ navigation }) {
const [exerciseCount, setExerciseCount] = useState(0);
const [workoutCount, setWorkoutCount] = useState(0);
you can then pass them along to WorkoutScreen or StatsScreen with something
like:
navigation.navigate("WorkoutScreen", { exerciseCount })
navigation.navigate("StatsScreen", { exerciseCount })
You'll probably have to read up on react-navigation's documentation for .navigate I'm not sure I remember this correctly.
In order to read the variable you can then:
export default function WorkoutScreen({ navigation }) {
const exerciseCount = navigation.getParam(exerciseCount);
return (
<View style={styles.container}>
<WorkoutTimer exerciseCount={exerciseCount} />
</View>
);
}
and finally show it in the WorkoutTimer:
export default function WorkoutTimer({ exerciseCount }) {
Of course that's just part of the solution, since you'll also have to pass
along a way to update your variables (setExerciseCount and setWorkoutCount).
I encourage you to read through the links I posted and try to get this to work.
After you've accumulated a few of these stateful variables, you might also want to look at Redux, but this is a bit much for now.
Your app looks cool, keep at it!
I ended up solving this problem with useContext if anyone is curious, it was hard to solve initially. But once I got my head around it, it wasn't too difficult to understand.
I created another file called exerciseContext with this code:
import React, { useState, createContext } from "react";
const ExerciseContext = createContext([{}, () => {}]);
const ExerciseProvider = (props) => {
const [state, setState] = useState(0);
//{ exerciseCount: 0, workoutCount: 0 }
return (
<ExerciseContext.Provider value={[state, setState]}>
{props.children}
</ExerciseContext.Provider>
);
};
export { ExerciseContext, ExerciseProvider };
and in App.js I used ExerciseProvider which allowed me to pass the data over the screens.
if (fontsLoaded) {
return (
<ExerciseProvider>
<NavigationContainer>
<MyTabs />
</NavigationContainer>
</ExerciseProvider>
);
} else {
return (
<AppLoading startAsync={getFonts} onFinish={() => setFontsLoaded(true)} />
);
}
}
I could call it with:
import { ExerciseContext } from "../components/ExerciseContext";
and
const [exerciseCount, setExerciseCount] = useContext(ExerciseContext);
This meant I could change the state too! Boom, solved! If anyone needs an explanation, let me know!
I think you have to use Mobx or Redux for state management. That will be more productive for you instead built-in state.

react-navigation drawer updating multiple times

I am building an application with React Native and React Navigation, I have made all the settings and it is working, however, when the drawer is fired the image is updated multiple times causing spikes and failures to trigger buttons contained in it.
e.g.:
I am using:
react: 16.8.3,
react-native: 0.59.1,
react-native-ui-kitten: ^3.1.2,
react-navigation: ^3.4.0
I was using version 3 of RN and to try to solve I went back to version 2 but without success.
I put some warnings in the method that executes the image and saw that it is called whenever there is this update.
I already changed the image in different sizes and formats but it also did not help.
I already tested on cell phones and emulators but with no success.
Drawer:
import React, { Component } from 'react';
import {
TouchableHighlight,
View,
ScrollView,
Image,
Platform,
StyleSheet,
} from 'react-native';
import {
RkStyleSheet,
RkText,
RkTheme,
} from 'react-native-ui-kitten';
import Icon from 'react-native-vector-icons/Ionicons';
import Routes from '../../config/navigation/routes';
import logo from '../../assets/smallLogo.png';
export default function SideNavigation(props) {
const onMenuItemPressed = item => {
props.navigation.navigate(item.id);
};
const renderIcon = () => (<Image style={styles.image} source={logo}/>);
const renderMenuItem = item => (
<TouchableHighlight style={styles.container} key={item.id} underlayColor={RkTheme.current.colors.button.underlay} activeOpacity={1} onPress={() => onMenuItemPressed(item)}>
<View style={styles.content}>
<View style={styles.content}>
<RkText style={styles.icon} rkType='moon primary xlarge'><Icon name={item.icon} size={25}/></RkText>
<RkText rkType='regular'>{item.title}</RkText>
</View>
{/*<RkText rkType='awesome secondaryColor small'>{FontAwesome.chevronRight}</RkText>*/}
</View>
</TouchableHighlight>
);
const renderMenu = () => Routes.map(renderMenuItem);
return (
<View style={styles.root}>
<ScrollView showsVerticalScrollIndicator={false}>
<View style={[styles.container, styles.content]}>
{renderIcon()}
</View>
{renderMenu()}
</ScrollView>
</View>
)
}
const styles = RkStyleSheet.create(theme => ({
container: {
height: 60,
paddingHorizontal: 16,
borderTopWidth: StyleSheet.hairlineWidth,
borderColor: theme.colors.border.base,
},
root: {
paddingTop: Platform.OS === 'ios' ? 20 : 0,
backgroundColor: theme.colors.screen.base
},
content: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
},
icon: {
marginRight: 13,
},
image:{
resizeMode: 'contain',
maxWidth: 125
}
}));
Drawer setup:
import React, {Component} from 'react';
import { View, Text} from 'react-native';
import Login from './screens/login';
import PasswordRecovery from './screens/passwordRecovery';
import Home from './screens/home';
import SideNavigator from './components/sideMenu';
import { bootstrap } from './config/bootstrap';
import {
createDrawerNavigator,
createStackNavigator,
createAppContainer
} from 'react-navigation';
import { withRkTheme } from 'react-native-ui-kitten';
import NavBar from './components/navBar';
import AppRoutes from './config/navigation/routesBuilder';
import Splash from './screens/splash';
bootstrap();
const renderHeader = (navigation, props) => {
const ThemedNavigationBar = withRkTheme(NavBar);
return (
<ThemedNavigationBar navigation={navigation} headerProps={props} />
);
};
const App = createStackNavigator({
Splash: Splash,
Login: Login,
PasswordRecovery: PasswordRecovery,
Main: createDrawerNavigator({
...AppRoutes
},{
contentComponent: props => {
const SideNav = withRkTheme(SideNavigator);
return <SideNav {...props}/>
}
}),
},
{
headerMode: 'none',
})
export default createAppContainer(App);
Routes setup:
import React from 'react';
import _ from 'lodash';
import { createStackNavigator } from 'react-navigation';
import { withRkTheme } from 'react-native-ui-kitten';
import transition from './transitions';
import Routes from './routes';
import NavBar from '../../components/navBar';
const main = {};
const flatRoutes = {};
const routeMapping = (route) => ({
screen: withRkTheme(route.screen),
title: route.title,
});
(Routes).forEach(route => {
flatRoutes[route.id] = routeMapping(route);
main[route.id] = routeMapping(route);
route.children.forEach(nestedRoute => {
flatRoutes[nestedRoute.id] = routeMapping(nestedRoute);
});
});
const renderHeader = (navigation, props) => {
const ThemedNavigationBar = withRkTheme(NavBar);
return (
<ThemedNavigationBar navigation={navigation} headerProps={props} />
);
};
const DrawerRoutes = Object.keys(main).reduce((routes, name) => {
const rawRoutes = routes;
rawRoutes[name] = {
name,
screen: createStackNavigator(flatRoutes, {
initialRouteName: name,
headerMode: 'screen',
cardStyle: { backgroundColor: 'transparent' },
transitionConfig: transition,
defaultNavigationOptions: ({ navigation }) => ({
gesturesEnabled: false,
header: (props) => renderHeader(navigation, props),
}),
}),
};
return rawRoutes;
}, {});
const AppRoutes = DrawerRoutes;

Trouble getting function from different component

I'm new to react native. I am trying to get a 'Key' from a different component. I mean I am trying to call a function from a different component, as a parent component. But, I'm totally jumbled with all these reference calls and all. Please suggest to me how to call a function from a different component.
// AddScreen.js
import React, { Component } from 'react';
import { AppRegistry, AsyncStorage, View, Text, Button, TextInput, StyleSheet, Image, TouchableHighlight, Linking } from 'react-native';
import styles from '../components/styles';
import { createStackNavigator } from 'react-navigation';
import History from '../components/History';
export default class AddScreen extends Component {
constructor(props) {
super(props);
this.state = {
myKey: '',
}
}
getKey = async () => {
try {
const value = await AsyncStorage.getItem('#MySuperStore:key');
this.setState({ myKey: value });
} catch (error) {
console.log("Error retrieving data" + error);
}
}
async saveKey(value) {
try {
await AsyncStorage.setItem('#MySuperStore:key', value);
} catch (error) {
console.log("Error saving data" + error);
}
}
componentDidMount() {
this.getKey();
}
render() {
const { navigate } = this.props.navigation;
return (
<View style={styles.MainContainer}>
<View style={styles.Date_input}>
<TextInput
placeholder="Add input"
value={this.state.myKey}
onChangeText={(value) => this.saveKey(value)}
/>
</View>
<View style={styles.getKeytext}>
<Text >
Stored key is = {this.state.myKey}
</Text>
</View>
<View style={styles.Historybutton}>
<Button
onPress={() => navigate('History')}
title="Press Me"
/>
</View>
</View>
)
}
}
//History.js
import React, { Component } from 'react';
import AddScreen from '../components/AddScreen';
import {
AppRegistry,
StyleSheet,
Text,
TextInput,
Button,
View,
AsyncStorage
} from 'react-native';
export default class History extends Component {
constructor(props) {
super(props);
this.state = {
myKey: ''
}
}
render() {call async function synchronously
return (
<View style={styles.container}>
<Button
style={styles.formButton}
onPress={this.onClick}
title="Get Key"
color="#2196f3"
accessibilityLabel="Get Key"
/>
<Text >
Stored key is = {this.state.mykey}
</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
padding: 30,
flex: 1,
backgroundColor: '#F5FCFF',
},
});
I just want to call the getKey function from the History component to get the myKey value on the History component's screen.
Please suggest to me, by taking my components as an example.
You just simply need to pass the key via navigation parameters.
<Button
onPress={() => navigate('History', { key: this.state.myKey })}
title="Press Me"
/>
and in your history component you can do
render() {
const key = this.props.navigation.getParam('key');
return (
// other code
)
}
You can read more about passing parameters here. https://reactnavigation.org/docs/en/params.html

Categories