I'm trying to create a simple stopwatch timer in React Native and I've created a new component called Chrono to handle the clock data(hours, minutes, etc.)
I'm triggering the clock count up on a button press in the following code:
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, AppRegistry, StyleSheet, Alert } from 'react-native';
import Chrono from './app/Chrono.js';
export default class morphX extends Component {
constructor(props) {
super(props)
this.state = {
randomCounter: 0
}
this.chrono = null
}
onItemPressed = () => {
this.chrono.startTimer()
}
updateRandomCounter = () => {
this.setState({
randomCounter: this.state.randomCounter + 1
})
}
clearCounter = () => {
this.setState({
randomCounter: 0
})
}
render() {
return (
<View style={styles.mainWrapper}>
<View style={styles.upperWrapper}>
<Chrono ref = { r => this.chrono = r} />
</View>
<View style={styles.bottomWrapper}>
<View style={styles.initialButtons}>
<TouchableOpacity
style={styles.touchableButton}
text="Let's Start!"
onPress={() => this.onItemPressed()}>
<Text style={styles.buttonTexts}>
Count Up!
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.touchableButton}
title="RESET!"
onPress={() => this.clearCounter()}>
<Text style={styles.buttonTexts}>
Reset!
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
}
AppRegistry.registerComponent('morphX', () => morphX);
and the startTimer is implemented in the Chrono.js component here:
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, AppRegistry, StyleSheet, Alert } from 'react-native';
class Chrono extends Component {
constructor(props) {
super(props)
this.state = {
hours: 0,
minutes: 0,
seconds: 0
}
}
// Chronometer start function
startTimer = () => {
console.log(this)
this.setTimeout(function() {
console.log('HeY!')
this.setState({
seconds: 1
})
}, 1000);
}
// Chronometer pause function
pauseTimer = () => {
}
// Chronometer reset function
resetTimer = () => {
}
render() {
return (
<View style={styles.clockWrapper}>
<Text style={styles.hourWrapper}>
{this.state.hours}
</Text>
<Text style={styles.colonWrapper}>
:
</Text>
<Text style={styles.minuteWrapper}>
{this.state.minutes}
</Text>
<Text style={styles.colonWrapper}>
:
</Text>
<Text style={styles.secondsWrapper}>
{this.state.seconds}
</Text>
</View>
)
}
}
export default Chrono
I'm facing the error
this.setTimeout is not a function
on the line where I'm calling the setTimeout for some reason. Why?
TimerMixin is not included with the default react-native. You have to install it yourself and then you can use this.setTimeout. Check here for detailed information.
This library does not ship with React Native - in order to use it on
your project, you will need to install it with
npm i react-timer-mixin --save
from your project directory.
Keep in mind that if you use ES6 classes for your React components
there is no built-in API for mixins. To use TimerMixin with ES6
classes, we recommend react-mixin.
I'm used setTimeout() without including react-timer-mixin, and its working fine in my application.
componentDidMount() {
const a = setTimeout(() => {
// do some stuff here
}, 100);
}
setTimeout() is global and actually window.setTimeout() in a browser, in React Native window is implied for global functions that use it. so this.setTimeout() should actually be just setTimeout().
Additionally see setTimeout in React Native that the callback function inside the setTimeout() changes the scope of "this"
Related
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.
I am currently building a component that includes a <TextInput which captures a value called userInput. I have a <TouchableOpacity> which then invokes a function which is declared outside of the component, however this particular function accepts the userInput as one of it's arguments.
I am struggling to figure out how to use the value assigned to userInput in the component's state, and pass it to myFunction. Is this possible and/or is there a better way to achieve this?
I did try moving the function inside the component, however this caused errors with the function.
Any help would be very much appreciated.
import React, {Component} from 'react';
import {Text, View} from 'react-native';
import {TextInput} from 'react-native-gesture-handler';
const myFunction = () => {
func()
.then(async user => {
const userInput = this.state.userInput;
await addToProfile(user, userInput);
})
.catch(err => {
console.error(err);
});
};
export default class Stackoverflow extends Component {
state = {
userInput: '',
};
constructor(props) {
super(props);
this.textInputComponent = React.createRef();
}
render() {
return (
<View>
<TextInput
ref={this.textInputComponent}
keyboardAppearance={'dark'}
value={this.state.userInput}
onChangeText={value => this.setState({userInput: value})}
/>
<TouchableOpacity
onPress={() => {
myFunction();
}}
/>
</View>
);
}
}
TouchableOpacity should have some child component within it to touch.
Lets take an example:
const callme= () => {
console.log("Pressed event called.....");
};
const mycomponent = () => {
return (
<TouchableItem
onPress={() => {
callme();
}}
useOpacity
>
<View style={[styles.container]}>
<Text style={[styles.mytextstyle}>Some title here</Text>
</View>
</TouchableItem>
);
};
I managed to solve the issue my moving the function declaration into the onPress() of the TouchableOpacity
I have created a Share icon which on click share's pdf or image file on social accounts like facebook, whatsapp, gmail, etc. I want to pass URL link of shareable file inside class component but getting error. If I hardcode the URL then it works fine but how can I pass URL which I receive from react navigation ?
Working code:
import React, { Component } from 'react';
import { Share, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
const shareOptions = {
title: 'Download Brochure',
url: 'https://cpbook.propstory.com/upload/project/brochure/5bc58ae6ca1cc858191327.pdf'
}
export default class Screen extends Component {
onSharePress = () => Share.share(shareOptions);
render() {
return (
<View>
<Text style={styles.infoTitle}>Share: </Text>
<TouchableOpacity onPress={this.onSharePress}>
<Icon style={{paddingLeft: 10, paddingTop: 10}}name="md-share" size={30} color="black"/>
</TouchableOpacity>
</View>
}
}
Above code works fine but below code gives error saying shareOptions is undefined. How can I overcome this problem ? I want to pass file URL inside shareOptions. I am getting file url from react navigation props i.e brochure.
Code:
import React, { Component } from 'react';
import { Share, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
export default class Screen extends Component {
onSharePress = () => Share.share(shareOptions); <---Getting error here shareOptions undefined
render() {
const project = this.props.navigation.getParam('project', null);
let { brochure } = project;
const shareOptions = {
title: 'Download Brochure',
url: brochure
}
return (
<View>
<Text style={styles.infoTitle}>Share: </Text>
<TouchableOpacity onPress={this.onSharePress}>
<Icon style={{paddingLeft: 10, paddingTop: 10}}name="md-share" size={30} color="black"/>
</TouchableOpacity>
</View>
)
}
How can I overcome above problem and how can I pass props i.e file URL in above case URL is in brochure props.
Just pass shareOptions object to onSharePress function as param and get shareOptions in onSharePress event handler function
import React, { Component } from 'react';
import { Share, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
export default class Screen extends Component {
onSharePress = (shareOptions) => {
Share.share(shareOptions);
}
render() {
const { navigation } = this.props;
const project = navigation.getParam('project', null);
const { brochure } = project;
const shareOptions = {
title: 'Download Brochure',
url: brochure
}
return (
<View>
<Text style={styles.infoTitle}>Share: </Text>
<TouchableOpacity onPress={() => this.onSharePress(shareOptions)}>
<Icon style={{paddingLeft: 10, paddingTop: 10}}name="md-share" size={30} color="black"/>
</TouchableOpacity>
</View>
)
}
}
You're not passing shareOptions to your share method, thus it is undefined.
There are many ways you could refactor your code, below you can see a more viable approach.
Since your render function used none of the logic above your return statement, I have simply migrated all of that into the onSharePress function, naturally if these differ, then you would want to pass it in via a parameter.
export default class Screen extends Component {
onSharePress = () => {
const project = this.props.navigation.getParam('project', null);
let { brochure } = project;
const shareOptions = {
title: 'Download Brochure',
url: brochure
}
Share.share(shareOptions);
}
render() {
return (
<View>
<Text style={styles.infoTitle}>Share: </Text>
<TouchableOpacity onPress={this.onSharePress>
<Icon style={{paddingLeft: 10, paddingTop: 10}}name="md-share" size={30} color="black"/>
</TouchableOpacity>
</View>
)
}
}
I'm working on a react native app that uses a timer component I made, the code for it is:
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
import styles from '../styles/style';
export default class Timer extends Component<{}> {
constructor(props) {
super();
this.state = {
time: 10,
timerStarted: false
}
}
startTimer() {
this.interval = setInterval(() => {
this.setState({
isFirstRender: !this.state.isFirstRender,
time: this.state.time-1,
isTimerRunning: true
});
}, 1000);
}
render() {
if (!this.state.isTimerRunning)
this.startTimer();
return (
<View style={styles.scoreBoard}>
<Text style={styles.timerText}>Time: {this.state.time}</Text>
</View>
);
}
}
This component has a state value, time, that counts down from 10, decrementing each second. When the timer reaches zero, I need it to somehow notify the component that called it when the timer is done. In my program my main js file is App.js, which calls my timer in its render function like this:
render () {
return (
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{flex: 1}}>
{/*This below is the component I need to return a value to this main class we are in*/}
<MyTimer />
</View>
</View>
);
}
I need my Timer class to return a value, perhaps a boolean, to the main class indicating that the time is up. My best guess is that maybe I can send a member function of my main class to the Timer class as a prop, but I'm not sure if that's how this works. I've tried different ways of accomplishing this, and I know that you can use props to send data to a component, but how do you retrieve data from a component? Thank you.
You pass a function from parent to child then invoke it in the child to update the parent:
onTimerEnd = () => //do something in parent
render () {
return (
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{flex: 1}}>
{/*This below is the component I need to return a value to this main class we are in*/}
<MyTimer onTimerEnd={this.onTimerEnd} />
</View>
</View>
);
}
Also I think you should start the timer in the constructor not in render, render will get called every time the component re-renders:
export default class Timer extends Component<{}> {
constructor(props) {
super();
this.state = {
time: 10,
}
this.startTimer(); //start timer
}
startTimer() {
localTime = this.state.time;
this.interval = setInterval(() => {
this.setState({
time: this.state.time-1,
}, () => {
if (this.state.time === 0) this.props.onTimerEnd() //invoke when timer hits 0
});
}, 1000);
}
render() {
return (
<View style={styles.scoreBoard}>
<Text style={styles.timerText}>Time: {this.state.time}</Text>
</View>
);
}
}
I'm trying to use NavigatorIOS so in my index.ios.js I got:
'use strict';
var React = require('react-native');
var Home = require('./App/Components/Home');
var {
AppRegistry,
StyleSheet,
NavigatorIOS
} = React;
var styles = StyleSheet.create({
container:{
flex: 1,
backgroundColor: '#111111'
}
});
class ExampleApp extends React.Component{
render() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
title: 'example',
component: Home
}} />
);
}
};
AppRegistry.registerComponent('exampleapp', () => ExampleApp);
module.exports = ExampleApp;
And then in the Home.js:
'use strict';
var React = require('react-native');
var Park = require('./Park');
var {
View,
StyleSheet,
Text,
TouchableHighlight
} = React;
var styles = StyleSheet.create({
...
});
class Home extends React.Component{
onPress() {
this.props.navigator.push({
title: 'Routed!',
component: Park
});
}
render() {
return (
<View style={styles.mainContainer}>
<Text> Testing React Native </Text>
<TouchableHighlight onPress={this.onPress} style={styles.button}>
<Text>Welcome to the NavigatorIOS . Press here!</Text>
</TouchableHighlight>
</View>
);
}
};
module.exports = Home;
The issue I have is that when I click on the TouchableHighlight triggering onPress(), I am getting an error:
"Error: undefined is not an object (evaluating 'this.props.navigator')
So it seems that it can't find the navigator from props but the navigator should be passed by NavigatorIOS?
Also it seems that the Home Component has this.props.navigator as per image:
Any hints?
I found a couple of links (people having exactly the same problem but that didn't help):
https://github.com/facebook/react-native/issues/416
undefined is not an object (evaluating 'this.props.navigator.push')
Since you're using ES6 you need to bind 'this'.
For example: onPress={this.onPress.bind(this)}
Edit: Yet another way that I've been using more recently is to use an arrow function on the function itself, since they will automatically bind the outside this.
onPress = () => {
this.props.navigator.push({
title: 'Routed!',
component: Park
});
};
You can bind the function to this in the constructor.
constructor(props) {
super(props);
this.onPress = this.onPress.bind(this);
}
Then use it when rendering without binding.
render() {
return (
<TouchableHighlight onPress={this.onPress} style={styles.button}>
<Text>Welcome to the NavigatorIOS. Press here!</Text>
</TouchableHighlight>
);
}
This has the advantage of being able to be used multiple times and also clean's up your render method.