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

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.

Related

How to implement multiple auth providers with react native expo-auth-session and firebase?

Hello and thanks in advance for your help.
I am struggling with implementing various auth providers with Firebase & expo-auth-session.
basically, the below code works well for Google login but I'd like to put all the "Google" logic in a separate file and add other files for additional providers. So that I can call the relevant function from the LoginScreen.js. Below the working code, I also pasted what I've tried but it resulted in having a hook error (Invalid hook call. Hooks can only be called inside of the body of a function component).
Has anyone an idea on how to manage this promptAsync() with multiple providers and separated files ?
import React, {useState, useEffect} from "react";
import { View, StyleSheet } from "react-native";
import { AppTitle, Screen, AppLink, SpecialText, AppButtonPrimary, AppText, ActivityIndicator } from "../../components";
import routes from "../../navigation/routes";
import colors from "../../config/colors";
import * as WebBrowser from 'expo-web-browser';
import { signInWithCredential, GoogleAuthProvider } from "firebase/auth";
import { auth } from '../../config/firebase';
import * as Google from 'expo-auth-session/providers/google';
import logger from '../../utility/logger'
WebBrowser.maybeCompleteAuthSession();
function LoginScreen({ navigation }) {
const [loadingPage, setLoadingPage] = useState(false)
const [request, response, promptAsync] = Google.useIdTokenAuthRequest(
{
clientId: 'xxxxxxxxxx.apps.googleusercontent.com',
},
);
useEffect(() => {
if (response?.type === 'success') {
setLoadingPage(true)
const { id_token } = response.params;
const credential = GoogleAuthProvider.credential(id_token);
logUser(auth, credential);
}
}, [response]);
async function logUser(auth, credential) {
try {
const result = await signInWithCredential(auth, credential)
const user = result.user // User is signed in
// Some code
} catch(err) {
logger.log(err)
// Some code
}
}
return (
<Screen style={styles.container}>
<ActivityIndicator visible={loadingPage}/>
<AppTitle style={styles.title} size={30}>Connectez-vous à votre compte</AppTitle>
<View style={styles.buttons}>
<AppButtonPrimary
text="Email"
icon="envelope-o"
onPress={() => navigation.navigate(routes.LOGIN_EMAIL_SCREEN)}
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
<AppButtonPrimary
text="Google"
icon="google"
onPress={() => {promptAsync()}} // <=== how to pass a function here instead of this promptAsync ?!
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
},
title: {
marginTop: 30,
marginHorizontal:10,
textAlign: 'center'
},
buttons: {
width: "100%",
alignItems: "center",
marginTop: 20,
}
});
export default LoginScreen;
// HERE IS WHAT I'VE TRIED
// LoginScreen.js
import React, {useState} from "react";
import { View, StyleSheet } from "react-native";
import { AppTitle, Screen, AppLink, SpecialText, AppButtonPrimary, AppText, ActivityIndicator } from "../../components";
import routes from "../../navigation/routes";
import colors from "../../config/colors";
import googleAuth from "../../auth/googleAuth";
import facebookAuth from "../../auth/facebookAuth";
function LoginScreen({ navigation }) {
const [loadingPage, setLoadingPage] = useState(false)
function googleSignIn() {
setLoadingPage(true)
googleAuth()
}
return (
<Screen style={styles.container}>
<ActivityIndicator visible={loadingPage}/>
<AppTitle style={styles.title} size={30}>Connectez-vous à votre compte</AppTitle>
<View style={styles.buttons}>
<AppButtonPrimary
text="Email"
icon="envelope-o"
onPress={() => navigation.navigate(routes.LOGIN_EMAIL_SCREEN)}
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
<AppButtonPrimary
text="Google"
icon="google"
onPress={googleSignIn} // <=== here
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
<AppButtonPrimary
text="Facebook"
icon="facebook"
onPress={facebookSignIn} // <=== this is the idea (having multiple providers with their own logic in a separate file)
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
</View>
</Screen>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
},
title: {
marginTop: 30,
marginHorizontal:10,
textAlign: 'center'
},
buttons: {
width: "100%",
alignItems: "center",
marginTop: 20,
}
});
export default LoginScreen;
// And the googleAuth.js
// Infrastructure
import { useEffect } from "react"
import { ResponseType } from 'expo-auth-session';
import * as Google from 'expo-auth-session/providers/google';
import * as WebBrowser from 'expo-web-browser';
// Firebase
import { signInWithCredential, GoogleAuthProvider } from "firebase/auth";
import { auth } from '../../app/config/firebase';
WebBrowser.maybeCompleteAuthSession();
function googleAuth() {
const [request, response, promptAsync] = Google.useIdTokenAuthRequest(
{
clientId: 'xxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
},
);
useEffect(() => {
if (response?.type === 'success') {
const { id_token } = response.params;
const credential = GoogleAuthProvider.credential(id_token);
logUser(auth, credential);
}
}, [response]);
async function logUser(auth, credential) {
try {
const result = await signInWithCredential(auth, credential)
const user = result.user // User is signed in
// Some Code
} catch(err) {
// Some Code
}
}
}
export default googleAuth

Hook Error After Implementing Redux Toolkit into Authentication

I keep getting an error when I run my app, and the screen is just white.
ERROR Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem., js engine: hermes
ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication). A frequent cause of the error is that the application entry file path is incorrect.
This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native., js engine: hermes
ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication). A frequent cause of the error is that the application entry file path is incorrect.
This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native., js engine: hermes
Full code is available on my GitHub, but let I'll past the areas of the code I was fiddling with:
https://github.com/astuertz/hashtagValues/commits/master
First, here's my store.js:
import { configureStore } from '#reduxjs/toolkit';
import activeImgReducer from '../features/counter/activeImgSlice';
import userAuthReducer from '../features/counter/userAuthSlice';
export default configureStore({
reducer: {
activeImg: activeImgReducer,
user: userAuthReducer
}
})
Here's my index.js:
/**
* #format
*/
import React from 'react';
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import store from './src/app/store';
import { Provider } from 'react-redux';
const reduxApp = () => (
<Provider store={store}>
<App />
</Provider>
);
AppRegistry.registerComponent(appName, () => reduxApp);
And my App.js (without styles):
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';
import Navigation from './src/components/routes/navigation';
import Amplify from 'aws-amplify';
import config from './src/aws-exports';
import { withAuthenticator } from 'aws-amplify-react-native';
Amplify.configure({
...config,
Analytics: {
disabled: true,
},
});
const App = () => {
return (
<View style={styles.root}>
<Navigation />
</View>
);
};
Here's my navigation.js (everything from the root stack down--the root stack has several other stacks nested in it):
const RootStack = createNativeStackNavigator();
const RootStackScreen = () => {
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState(null);
useEffect(() => {
setTimeout(() => {
setIsLoading(!isLoading);
setUser('user');
}, 2500)
}, []);
return (
<RootStack.Navigator screenOptions={{
animationEnabled: false,
headerShown: false,
presentation: 'modal',
}}>
{isLoading ? (
<RootStack.Screen name="LoadingScreen" component={LoadingScreen} />
) : user ? (
<RootStack.Screen name="AppTabs" component={AppTabsScreen} />
) : (
<RootStack.Screen name="AuthStackScreen" component={AuthStackScreen} />
)}
<RootStack.Screen name="Gallery" component={GalleryScreen} options={{
animationEnabled: true,
cardStyle: {
backgroundColor: 'black',
},
}}/>
</RootStack.Navigator>
);
};
export default () => {
return (
<NavigationContainer>
<RootStackScreen />
</NavigationContainer>
);
};
I now have it back to the way I had it before the error started occurring.
The only other thing I was messing with is the Sign In Screen and the Profile Screen (with Sign Out). Here's the Sign In Screen:
import React, {useState} from 'react';
import {
View,
Text,
StyleSheet,
Image,
Dimensions,
TextInput,
Button,
TouchableWithoutFeedback,
Keyboard,
TouchableOpacity,
Alert,
} from 'react-native';
import logo from '../../graphics/Values_logo.png';
import { useNavigation } from '#react-navigation/native';
import { Auth } from 'aws-amplify';
import { useSelector, useDispatch, } from 'react-redux';
import { validateUser } from '../../features/counter/userAuthSlice';
const WIDTH = Dimensions.get("window").width;
const HEIGHT = Dimensions.get("window").height;
const SignIn = () => {
const navigation = useNavigation();
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const onPressSignIn = async () => {
if (email.length < 1 || password.length < 1) return Alert.alert('no input', 'please input an email and password to log in.');
try {
const u = await Auth.signIn(email, password);
dispatch(validateUser(u));
} catch(e) {
Alert.alert('Login Failed!', e.message);
return;
}
Alert.alert('Login Successful!');
return;
}
const renderLogo = (
<Image
source={logo}
style={styles.logoSize}
resizeMode='contain'
/>
);
const inputElements = (
<>
<TextInput
placeholder='email'
value={email}
onChangeText={setEmail}
style={styles.textInput}
/>
<TextInput
placeholder='password'
value={password}
onChangeText={setPassword}
style={styles.textInput}
secureTextEntry={true}
/>
<TouchableOpacity
onPress={onPressSignIn}
style={styles.button}
>
<Text style={styles.buttonText}>Sign In</Text>
</TouchableOpacity>
<Text
style={styles.hyperlink}
>Forgot Password?</Text>
<Text
style={styles.hyperlink}
onPress={() => navigation.push("SignUp")}
>Sign Up</Text>
</>
);
return (
<TouchableWithoutFeedback
onPress={() => Keyboard.dismiss()}>
<View style={styles.pageContainer}>
<View style={styles.logo}>
{renderLogo}
</View>
<View style={styles.inputContainer} >
{inputElements}
</View>
</View>
</TouchableWithoutFeedback>
);
}
And the Profile Screen:
import React from 'react';
import {View, Text, StyleSheet, Button, Alert,} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Auth } from 'aws-amplify';
import { useSelector, useDispatch, } from 'react-redux';
import { signOutUser } from '../../features/counter/userAuthSlice';
const dispatch = useDispatch();
const onSignOut = async () => {
try {
await Auth.signOut();
dispatch(signOutUser());
} catch (error) {
Alert.alert('error signing out: ', error.message);
return;
}
Alert.alert('Sign Out Successful!');
}
const ProfileScreen = () => {
return (
<SafeAreaView style={styles.pageContainer}>
<Text>ProfileScreen</Text>
<Button title="Sign Out" onPress={onSignOut} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
root: {
flex: 1
},
pageContainer: {
justifyContent: 'center',
alignItems: 'center',
flex: 1,
width: '100%'
}
});
export default ProfileScreen;
I'm really not sure what I did to break my app or how to fix it.
In Profile Screen, you are calling const dispatch = useDispatch(); which is outside of the component and is an invalid call. It has to be called in inside ProfileScreen. When you are not sure where the problem occurs try commenting you code and see if it works without them. Like commenting your screens one by one would help you find which screen the error is caused in etc.

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

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.

JS React Native - importing local database - with Expo - works only in Web view, not at smartphone

i implemented a little app for learning, and imported data from a local database. I realized it with this code:
import { StatusBar } from 'expo-status-bar';
import { Platform, FlatList, StyleSheet, Text, View, TextInput, Button, SafeAreaView, ScrollView } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { Icon } from 'react-native-elements'
import * as React from 'react';
import { ListItem, Avatar} from 'react-native-elements';
import { Header } from 'react-native-elements';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { List, Colors } from 'react-native-paper';
import Moment from 'moment';
const MyComponent = () => {
const [expanded, setExpanded] = React.useState(true);
}
const handlePress = () => setExpanded(!expanded);
export default class Db extends React.Component {
state = {
data: [],
}
componentWillMount() { //load data from a remote endpoint (where to instantiate the network request)
this.fetchData();
}
fetchData = async () => {
const response = await fetch('http://localhost:3000/Ph');
const json = await response.json();
this.setState({data: json}); //js object notation
}
keyExtractor = (x, i) => i //extract item
renderItem = ({ item }) => (
<List.Section>
<List.Accordion
theme = {{colors: { primary: '#00A1DE'}}}
style= {{ backgruondColor: '#00A1DE' }}
titleStyle = {styles.titleContainer}
title = {item.Name}
left= { props => <List.Icon {...props} icon = "account" /> }>
<List.Item
titleStyle={styles.textContainer}
title= {item.Geburtsdatum} left= { props => <List.Icon {...props} icon = "cake-variant" /> }></List.Item>
<List.Item
titleStyle={styles.textContainer}
title={item.hobbies} left= { props => <List.Icon {...props} icon = "table-tennis" /> }></List.Item>
</List.Accordion>
</List.Section>
)
render() {
return (
<FlatList
keyExtractor={this.keyExtractor}
data = {this.state.data}
renderItem = { this.renderItem }
/>
)
}
}
const styles = StyleSheet.create({
textContainer: {
flex: 1,
height: '90%',
width: '90%',
},
titleContainer: {
fontWeight: 'bold',
}
});
This works well with the Expo web view, but when I scan the QR Code from my Iphone, the data are not shown. Only footer and header with bottom menue...
I get also the yellow warning because of my ComponentWillMount - function. But do not know, if this is the real reason for my problem. What do you think, why it does not work on my Iphone? I am using the newest expo Version, the newest React Native version and so on... The Code above works well with the Web View from Expo...
Add you IP address instead of localhost
1.) Run cmd
2.) type ipconfig
3.) Scroll Down to IPv4 Address. . . . . . . . . . . : 192.168.**.**
4.) Copy this IP and replace it in place of localhost
5.) Done
Get your IP like this
So Suppose if your IPv4 address is - 192.168.100.84 then
Your fetch should look like this
await fetch('http://192.168.100.84:3000/Ph');
Your component should look like this..Notice I've used Function component here
import * as React from 'react';
import Constants from 'expo-constants';
import { StatusBar } from 'expo-status-bar';
import {
Platform,
FlatList,
StyleSheet,
Text,
View,
TextInput,
Button,
SafeAreaView,
ScrollView,
} from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { Icon } from 'react-native-elements';
import { ListItem, Avatar } from 'react-native-elements';
import { Header } from 'react-native-elements';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { List, Colors } from 'react-native-paper';
import Moment from 'moment';
const BaseURL = '192.168.100.84'; // Here your IPv4 address
export default function App() {
const [Data, setData] = React.useState([]);
React.useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch(`http://${BaseURL}:3000/Ph`);
const json = await response.json();
setData(json);
};
const keyExtractor = (x, i) => i; //extract item
const renderItem = ({ item }) => (
<List.Section>
<List.Accordion
theme={{ colors: { primary: '#00A1DE' } }}
style={{ backgruondColor: '#00A1DE' }}
titleStyle={styles.titleContainer}
title={item.Name}
left={(props) => <List.Icon {...props} icon="account" />}>
<List.Item
titleStyle={styles.textContainer}
title={item.Geburtsdatum}
left={(props) => (
<List.Icon {...props} icon="cake-variant" />
)}></List.Item>
<List.Item
titleStyle={styles.textContainer}
title={item.hobbies}
left={(props) => (
<List.Icon {...props} icon="table-tennis" />
)}></List.Item>
</List.Accordion>
</List.Section>
);
return (
<FlatList
keyExtractor={keyExtractor}
data={Data}
renderItem={renderItem}
/>
);
}
const styles = StyleSheet.create({
textContainer: {
flex: 1,
height: '90%',
width: '90%',
},
titleContainer: {
fontWeight: 'bold',
},
});

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.

Categories