There is a count down timer activated by a play/pause button. setInterval is called in run passing in two functions countDown and vibrate. countDown decrements the state of the timer.
Within vibrate I want the phone to vibrate when the timer reaches a certain number, however, within vibrate "timer" is always 1500 until the pause button is pressed, and when play is pressed again "timer" is the value it had when paused.
In clockify which displays the time, "timer" is the current value of "timer" and the timer counts down.
I've read for hours here trying to fix this (which seems like a closure issue?) but could not come up with a solution, other than refactoring this into a class and use this.state
import { StatusBar } from 'expo-status-bar'
import React, { useState } from 'react'
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { AntDesign } from '#expo/vector-icons'
import { FontAwesome } from '#expo/vector-icons'
let intervalId = null
export default function App() {
const [timer, setTimer] = useState(1500),
[running, setRunning] = useState(false)
const reset = () => {
setTimer(1500)
setRunning(false)
clearInterval(intervalId)
}
const playPauseIcon = () => {
if (!running) {
return ( <FontAwesome name="play" style={styles.icons} /> )
} else {
return ( <FontAwesome name="pause" style={styles.icons} /> )
}
}
const playPause = () => {
if (!running) {
setRunning(true)
run()
} else {
setRunning(false)
this.clearInterval(intervalId)
}
}
const run = () => {
intervalId = setInterval(consolidateFunc, 1000)
}
const consolidateFunc = () => {
countDown()
vibrate()
}
const countDown = () => {
console.log("timer in countDown" + timer) // always logs 1500, 1500, 1500 until the play/pause button is pressed
setTimer(timer => timer - 1)
}
const vibrate = () => {
console.log("timer in buzz " + timer) // always logs 1500, 1500, 1500 until the play/pause button is pressed
if (timer < 1496) {
// vibrate
console.log("vibrate")
clearInterval(intervalId)
}
}
const clockify = () => {
console.log("timer in clock " + timer) //counts down 1500, 1499, 1498, etc.
const minutes = Math.floor(timer / 60),
seconds = timer % 60
if (seconds < 10 && minutes < 10) {
return "0" + minutes + ":" + "0" + seconds;
} else if (seconds < 10) {
return minutes + ":" + "0" + seconds;
} else {
return minutes + ":" + seconds;
}
}
return (
<View style={styles.container}>
<View style={styles.controls}>
<TouchableOpacity onPress={playPause} style={styles.reset}>{playPauseIcon()}</TouchableOpacity>
<TouchableOpacity style={styles.reset} onPress={reset}>
<FontAwesome name="undo" style={styles.icons} />
</TouchableOpacity>
</View>
<Text style={styles.status}>Time Remaining</Text>
<Text style={styles.timeLeft}>{clockify()}</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
status: {
fontSize: 30,
paddingTop: 20
},
timeLeft: {
fontSize: 30
},
controls: {
flexDirection: "row",
alignItems: "center"
},
icons: {
fontSize: 25,
color: "black",
paddingLeft: 10
},
reset: {
paddingTop: 15,
paddingRight: 5,
paddingLeft: 5
}
});
View on snack: https://snack.expo.io/R1ST9Ct4U
I can make it work using a class but I would prefer not to if possible, it seems like there should be a way.
import { StatusBar } from 'expo-status-bar'
import React, { useState } from 'react'
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { AntDesign } from '#expo/vector-icons'
import { FontAwesome } from '#expo/vector-icons'
const defaultState = {timer: 1500, running: false };
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = defaultState
this.reset = this.reset.bind(this)
this.clockify = this.clockify.bind(this);
this.run = this.run.bind(this);
this.consolidateFunc = this.consolidateFunc.bind(this)
this.countDown = this.countDown.bind(this)
this.vibrate = this.vibrate.bind(this)
this.playPauseIcon= this.playPauseIcon.bind(this)
this.playPause = this.playPause.bind(this)
}
reset() {
this.setState(defaultState)
clearInterval(this.intervalId)
}
clockify() {
const minutes = Math.floor(this.state.timer / 60);
const seconds = this.state.timer % 60;
if (seconds < 10 && minutes < 10) {
return "0" + minutes + ":" + "0" + seconds;
} else if (seconds < 10) {
return minutes + ":" + "0" + seconds;
} else {
return minutes + ":" + seconds;
}
}
run() {
this.intervalId = setInterval(this.consolidateFunc, 1000);
}
consolidateFunc() {
this.countDown();
this.vibrate();
}
countDown() {
console.log("timer in countDown: " + this.state.timer)
this.setState({ timer: this.state.timer - 1 });
}
vibrate() {
console.log("timer in vibrate: " + this.state.timer)
if (this.state.timer < 1496) {
// vibrate
console.log("vibrate")
clearInterval(this.intervalId)
}
}
playPauseIcon() {
if (!this.state.running) {
return ( <FontAwesome name="play" style={styles.icons} /> )
} else {
return ( <FontAwesome name="pause" style={styles.icons} /> )
}
}
playPause() {
if (!this.state.running) {
this.setState({ running: true });
this.run();
} else {
this.setState({ running: false });
clearInterval(this.intervalId);
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.controls}>
<TouchableOpacity onPress={this.playPause} style={styles.reset}>{this.playPauseIcon()}</TouchableOpacity>
<TouchableOpacity style={styles.reset} onPress={this.reset}>
<FontAwesome name="undo" style={styles.icons} />
</TouchableOpacity>
</View>
<Text style={styles.status}>Time Remaining</Text>
<Text style={styles.timeLeft}>{this.clockify()}</Text>
<StatusBar style="auto" />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
status: {
fontSize: 30,
paddingTop: 20
},
timeLeft: {
fontSize: 30
},
controls: {
flexDirection: "row",
alignItems: "center"
},
icons: {
fontSize: 25,
color: "black",
paddingLeft: 10
},
reset: {
paddingTop: 15,
paddingRight: 5,
paddingLeft: 5
}
});
View as class on snack: https://snack.expo.io/mIG7TqB4I
I took a walk and the answer came to me.
The answer was to to eliminate the consolidateFunc, pass countDown to setInterval, move the code inside vibrate into useEffect, delete vibrate.
import { StatusBar } from 'expo-status-bar'
import React, { useState, useEffect } from 'react'
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { AntDesign } from '#expo/vector-icons'
import { FontAwesome } from '#expo/vector-icons'
let intervalId = null
export default function App() {
const [timer, setTimer] = useState(1500),
[running, setRunning] = useState(false)
const reset = () => {
setTimer(1500)
setRunning(false)
clearInterval(intervalId)
}
const playPauseIcon = () => {
if (!running) {
return ( <FontAwesome name="play" style={styles.icons} /> )
} else {
return ( <FontAwesome name="pause" style={styles.icons} /> )
}
}
const playPause = () => {
if (!running) {
setRunning(true)
run()
} else {
setRunning(false)
clearInterval(intervalId)
}
}
const run = () => {
intervalId = setInterval(countDown, 1000)
}
const countDown = () => {
setTimer(timer => timer - 1)
}
const clockify = () => {
//console.log("timer in clock " + timer)
const minutes = Math.floor(timer / 60),
seconds = timer % 60
if (seconds < 10 && minutes < 10) {
return "0" + minutes + ":" + "0" + seconds;
} else if (seconds < 10) {
return minutes + ":" + "0" + seconds;
} else {
return minutes + ":" + seconds;
}
}
useEffect(() => {
console.log("timer in useEffect " + timer)
if (timer < 1496) {
// vibrate
console.log("vibrate")
clearInterval(intervalId)
}
})
return (
<View style={styles.container}>
<View style={styles.controls}>
<TouchableOpacity onPress={playPause} style={styles.reset}>{playPauseIcon()}</TouchableOpacity>
<TouchableOpacity style={styles.reset} onPress={reset}>
<FontAwesome name="undo" style={styles.icons} />
</TouchableOpacity>
</View>
<Text style={styles.status}>Time Remaining</Text>
<Text style={styles.timeLeft}>{clockify()}</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
status: {
fontSize: 30,
paddingTop: 20
},
timeLeft: {
fontSize: 30
},
controls: {
flexDirection: "row",
alignItems: "center"
},
icons: {
fontSize: 25,
color: "black",
paddingLeft: 10
},
reset: {
paddingTop: 15,
paddingRight: 5,
paddingLeft: 5
}
});
Related
I have a custom radio button component. I am importing that to my parent form to create a dynamic fields of radio button from JSON file. I have multiple other views too. I am getting these views values and creating a JSON array in my parent form. I am stuck in how to get my custom radio buttons values and pass them to my method where i am creating JSON array of values.Here is my custom component radio button code
import React, { Component } from "react";
import { View, TouchableOpacity, Text, StyleSheet } from "react-native";
export default class RadioButton extends Component {
state = {
value: null,
};
render() {
const { PROP } = this.props;
const { value } = this.state;
return (
<View>
{PROP.map((res) => {
return (
<View key={res.key} style={styles.container}>
<Text style={styles.radioText}>{res.text}</Text>
<TouchableOpacity
style={styles.radioCircle}
onPress={() => {
this.setState({
value: res.text,
});
}}
>
{value === res.text && <View style={styles.selectedRb} />}
</TouchableOpacity>
</View>
);
})}
<Text> Selected: {this.state.value} </Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
marginBottom: 15,
alignItems: "center",
flexDirection: "row",
justifyContent: "space-between",
},
radioText: {
marginRight: 35,
color: "#000",
},
radioCircle: {
height: 20,
width: 20,
marginRight: 10,
borderRadius: 100,
borderWidth: 2,
borderColor: "#3740ff",
alignItems: "center",
justifyContent: "center",
},
selectedRb: {
width: 15,
height: 15,
borderRadius: 15,
backgroundColor: "#3740ff",
},
result: {
marginTop: 20,
color: "white",
fontWeight: "600",
backgroundColor: "#F3FBFE",
},
});
This is my main class.
import React, { Component } from "react";
import CheckBox from "#react-native-community/checkbox";
import { View, TextInput, Button, StyleSheet, Text } from "react-native";
const data = require("../json/registration.json");
import MyRadioButton from "../component/MyRadioButton";
class Registration extends Component {
constructor(props) {
super(props);
this.state = {
itemstorender: [],
inputData: [],
checked: "",
};
}
addValues = (value, index) => {
let dataArray = this.state.inputData;
let checkBool = false;
if (dataArray.length !== 0) {
dataArray.forEach((element) => {
if (element.index === index) {
element.value = value;
checkBool = true;
}
});
}
if (checkBool) {
this.setState({
inputData: dataArray,
});
} else {
dataArray.push({ value: value, index: index });
this.setState({
inputData: dataArray,
});
// console.log('Data',dataArray);
}
};
getValues = () => {
console.log("Data", this.state.inputData);
};
componentDidMount() {
this.renderData();
}
hideComponent(data) {
// console.log("checkd",data)
this.setState({
checked: data,
});
console.log(this.state.checked);
}
renderData = () => {
const result = data.info;
var itemstorenderLocal = [];
for (var i = 0; i < result.length; i++) {
if (result[i].element == "TextInput") {
let i_id = result[i].id;
console.log("Ids : ", i_id);
itemstorenderLocal.push(
<TextInput
key={result[i].id}
placeholder={result[i].label}
onChangeText={(value) => this.addValues(value, i_id)}
/>
);
this.setState({ itemstorenderLocal });
}else if (result[i].element == "RadioButtons") {
let i_id = result[i].id;
// let options = console.log("Ids : ", i_id);
itemstorenderLocal.push(
<Text>{result[i].label}</Text>,
<View style={styles.container}>
<MyRadioButton
PROP={result[i].options}
/>
</View>
);
this.setState({ itemstorenderLocal });
} else if (result[i].element == "button") {
itemstorenderLocal.push(
<Button
key={result[i].id}
title={result[i].label}
onPress={() => this.getValues()}
/>
);
}
}
this.setState({
itemstorender: itemstorenderLocal,
});
};
render() {
return <View>{this.state.itemstorender}</View>;
}
}
export default Registration;
I'm trying to make a timer with hooks but when I start the app, it's still counting only secs down, even if secs below 0. I couldn't find where the problem is
import React, { useState, useEffect } from 'react'
import { Text, View } from 'react-native'
export default App = () => {
const [mins, setMins] = useState(2)
const [secs, setSecs] = useState(2)
useEffect(() => {
setInterval(() => {
if (secs <= 0) {
if (mins <= 0) alert('end')
else {
setMins(m => m - 1)
setSecs(59)
}
}
else setSecs(s => s - 1)
}, 1000)
}, [])
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 40 }}>
{mins}:{secs < 10 && 0}{secs}
</Text>
</View>
)
}
This should work:
Don't forget to return from useEffect and clearInterval.
import React, { useState, useEffect } from 'react'
import { Text, View } from 'react-native'
export default App = () => {
const [mins, setMins] = useState(2)
const [secs, setSecs] = useState(2)
useEffect(() => {
const timerId = setInterval(() => {
if (secs <= 0) {
if (mins <= 0) alert('end')
else {
setMins(m => m - 1)
setSecs(59)
}
}
else setSecs(s => s - 1)
}, 1000)
return () => clearInterval(timerId);
}, [secs, mins])
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 40 }}>
{mins}:{secs < 10 && 0}{secs}
</Text>
</View>
)
}
Firstly, the React.useState, like the this.setState, it is an async function, you should not call them at the same time.
Secondly,React.useEffect like the componentDidMount,componentDidUpdate,componentWillMount. you have to return when the your condition is not match and clearInterval.
my solution code is the following:
import React, { useState, useEffect } from 'react'
import { Text, View } from 'react-native'
export default App = () => {
const [time, setTime] = useState({mins:2,secs:2})
useEffect(() => {
if(time.mins<0){ if(timerId) {clearInterval(timerId)} return}
const timerId = setInterval(() => {
if (time.secs <= 0) {
if (time.mins <= 0) {
setTime({...time,mins:time.mins-1,secs:time.secs})
alert('end')
}
else {
setTime({...time,mins:time.mins-1,secs:59})
}
}
else setTime({...time,mins:time.mins,secs:time.secs-1})
}, 1000)
return () => clearInterval(timerId);
}, [time])
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 40 }}>
{time.mins>=0 ? time.mins : 0}:{time.secs < 10 && 0}{time.secs}
</Text>
</View>
)
}
But for the react-native suggest way, the setState should use in the component,
the pure component should not React.useState, if you need it, you could use React. Component
I need to change the selectedDay to the current day every time the user navigates to the calendar screen.
I set the selected date as today and can already detect when the user acesses the screen, but even when I change the state or force update, the calendar doesn't move to the date I set as selected.
componentDidUpdate = async (prevProps) => {
if (prevProps.isFocused !== this.props.isFocused && this.props.isFocused) {
this.forceUpdate()
}
}
and
<Agenda
items={events}
pastScrollRange={50}
futureScrollRange={50}
onDayPress={this.setCurrentDate}
loadItemsForMonth={this.loadItems}
renderItem={this.renderItem}
renderEmptyDate={this.renderEmptyDate}
rowHasChanged={this.rowHasChanged}
selected={this.state.today}
/>
With forceUpdate or changing some arbitrary state, the calendar stays in the currently selected date. I wanted it to go back to today.
You can set the ref for Agenda component
ref={ref => {
this.agenda = ref;
}}
Then on componentDidUpdate, change date to current date by calling function onDayChange of Agenda component
if (prevProps.isFocused !== this.props.isFocused) {
setTimeout(() => {
this.agenda.onDayChange(this.state.today);
}, 500);
Complete Code
import React, { Component } from "react";
import { Agenda } from "react-native-calendars";
import { Text, View, StyleSheet } from "react-native";
import { withNavigationFocus } from "react-navigation";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
items: {},
today: new Date().toISOString().split("T")[0]
};
}
componentDidUpdate(prevProps) {
if (prevProps.isFocused !== this.props.isFocused) {
setTimeout(() => {
this.agenda.onDayChange(this.state.today);
}, 500);
}
}
render() {
return (
<View style={{ flex: 1 }}>
<Text
style={{ padding: 30, fontWeight: "bold", textAlign: "center" }}
onPress={() => this.props.navigation.navigate("NewScreen")}
>
Go To Next Screen
</Text>
<Agenda
items={this.state.items}
loadItemsForMonth={this.loadItems.bind(this)}
selected={this.state.today}
renderItem={this.renderItem.bind(this)}
renderEmptyDate={this.renderEmptyDate.bind(this)}
rowHasChanged={this.rowHasChanged.bind(this)}
onDayPress={day => {
console.log("selected day", day);
}}
ref={ref => {
this.agenda = ref;
}}
/>
</View>
);
}
loadItems(day) {
setTimeout(() => {
for (let i = -15; i < 85; i++) {
const time = day.timestamp + i * 24 * 60 * 60 * 1000;
const strTime = this.timeToString(time);
if (!this.state.items[strTime]) {
this.state.items[strTime] = [];
const numItems = Math.floor(Math.random() * 5);
for (let j = 0; j < numItems; j++) {
this.state.items[strTime].push({
name: "Item for " + strTime,
height: Math.max(50, Math.floor(Math.random() * 150))
});
}
}
}
//console.log(this.state.items);
const newItems = {};
Object.keys(this.state.items).forEach(key => {
newItems[key] = this.state.items[key];
});
this.setState({
items: newItems
});
}, 1000);
// console.log(`Load Items for ${day.year}-${day.month}`);
}
renderItem(item) {
return (
<View style={[styles.item, { height: item.height }]}>
<Text>{item.name}</Text>
</View>
);
}
renderEmptyDate() {
return (
<View style={styles.emptyDate}>
<Text>This is empty date!</Text>
</View>
);
}
rowHasChanged(r1, r2) {
return r1.name !== r2.name;
}
timeToString(time) {
const date = new Date(time);
return date.toISOString().split("T")[0];
}
}
const styles = StyleSheet.create({
item: {
backgroundColor: "white",
flex: 1,
borderRadius: 5,
padding: 10,
marginRight: 10,
marginTop: 17
},
emptyDate: {
height: 15,
flex: 1,
paddingTop: 30
}
});
export default withNavigationFocus(Home);
Context
Im want to get a countdown value which I then want to pass as prop to a separate component animating a progress bar. the data.json contains two type of elements:
text
question
the text will present the user an explanation. when the user pushes the button another text can be shown or a question. when it is a question the user will have certain amount of time for the answer. the progress bar will indicate the remaining time and shall only be shown when the current element is a question.
Problem
My problem is that the screen update for the current value is super slow and I can't see the reason. here is a simplified version of my code. the progress bar component will replace the <Text>{this.state.value}</Text> within the renderButtons function
Versions
I am running react native 0.60.
Update 1
I found out that when I trigger the countdown function by an onPress event, it works. but if I call it directly it doesn't. Why is that? How could I achieve it without the onPress
Update 2
the problem is that due to the animation the state value gets updated an by that a serenader gets triggered, which causes the function to be called each time, which is causing the lagging. this workaround seems to help but it feels ugly.
what would be a better approach to handle the re-rendering issue?
countdown = type => {
if (!this.state.countdownRunning) {
this.setState({ countdownRunning: true });
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(() => {
console.log('Animation DONE');
this.setState({ value: 100, percent: new Animated.Value(100) });
this.onPressNext();
this.setState({ countdownRunning: false });
}); // Start the animation
}
}
};
Lagging
import React, { Component } from 'react';
import { View, Text, StyleSheet, Animated, Easing } from 'react-native';
import { Button } from 'react-native-paper';
import Card from '../components/Card';
import data from '../data/data.json';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5
},
surface: {
padding: 20,
margin: 20,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
elevation: 12
}
});
class Home extends Component {
constructor(props) {
super(props);
this.countdown = this.countdown.bind(this);
this.state = {
i: 0,
questions: data,
value: 100,
percent: new Animated.Value(100)
};
}
onPressNext = () => {
const { i } = this.state;
if (i < 17) {
this.setState({ i: i + 1 });
} else {
this.setState({ i: 0 });
}
};
onPressBack = () => {
const { i } = this.state;
if (i > 0) {
this.setState({ i: i - 1 });
} else {
this.setState({ i: 17 });
}
};
countdown = type => {
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(); // Start the animation
}
};
renderButtons = type => {
if (type === 'question') {
this.countdown(type);
return (
<View>
<Text>{this.state.value}</Text>
</View>
);
}
return (
<View style={{ flexDirection: 'row' }}>
<Button mode="text" onPress={() => this.onPressBack()}>
Zurück
</Button>
<Button mode="contained" onPress={() => this.onPressNext(type)}>
Weiter
</Button>
</View>
);
};
render() {
const { i, questions } = this.state;
const { type, header, content } = questions.data[i.toString()];
return (
<View style={styles.container}>
<View style={{ flex: 2, justifyContent: 'flex-end' }}>
<Card>
<Text>{header}</Text>
<Text>{content}</Text>
</Card>
</View>
<View style={{ flex: 2 }}>{this.renderButtons(type)}</View>
</View>
);
}
}
export default Home;
No Lagging
import React, { Component } from 'react';
import { View, Text, StyleSheet, Animated, Easing } from 'react-native';
import { Button } from 'react-native-paper';
import Card from '../components/Card';
import data from '../data/data.json';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5
},
surface: {
padding: 20,
margin: 20,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
elevation: 12
}
});
class Home extends Component {
constructor(props) {
super(props);
this.countdown = this.countdown.bind(this);
this.state = {
i: 0,
questions: data,
value: 100,
percent: new Animated.Value(100)
};
}
onPressNext = () => {
const { i } = this.state;
if (i < 17) {
this.setState({ i: i + 1 });
} else {
this.setState({ i: 0 });
}
};
onPressBack = () => {
const { i } = this.state;
if (i > 0) {
this.setState({ i: i - 1 });
} else {
this.setState({ i: 17 });
}
};
countdown = type => {
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(); // Start the animation
}
};
renderButtons = type => {
if (type === 'question') {
return (
<View>
<Text>{this.state.value}</Text>
<Button mode="contained" onPress={() => this.countdown(type)}>
Weiter
</Button>
</View>
);
}
return (
<View style={{ flexDirection: 'row' }}>
<Button mode="text" onPress={() => this.onPressBack()}>
Zurück
</Button>
<Button mode="contained" onPress={() => this.onPressNext(type)}>
Weiter
</Button>
</View>
);
};
render() {
const { i, questions } = this.state;
const { type, header, content } = questions.data[i.toString()];
return (
<View style={styles.container}>
<View style={{ flex: 2, justifyContent: 'flex-end' }}>
<Card>
<Text>{header}</Text>
<Text>{content}</Text>
</Card>
</View>
<View style={{ flex: 2 }}>{this.renderButtons(type)}</View>
</View>
);
}
}
export default Home;
I am trying to build an interface similar to the Snapchat's interface where you can swipe left/right/up to access different screen/navigator. Currently I am using the DrawerNavigator but it's kind of janky because I am using a DrawerNavigator on top of another DrawerNavigator.
Does anyone have a good suggestion on the best way to do this?
Thanks!
The following code is implementing easy 4-way swipe navigation across views declaratively in under 20 lines, no Javascript!
import Swiper from 'react-native-swiper'
import randomcolor from 'randomcolor'
const {
View,
Text,
StyleSheet
} = React
var styles = StyleSheet.create({
container: {
flex: 1
},
view: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
})
class TitleText extends React.Component {
render() {
return (
<Text style={{ fontSize: 48, color: 'white' }}>
{this.props.label}
</Text>
)
}
}
class Home extends React.Component {
viewStyle() {
return {
flex: 1,
backgroundColor: randomcolor(),
justifyContent: 'center',
alignItems: 'center',
}
}
render() {
return (
<Swiper
loop={false}
showsPagination={false}
index={1}>
<View style={this.viewStyle()}>
<TitleText label="Left" />
</View>
<Swiper
horizontal={false}
loop={false}
showsPagination={false}
index={1}>
<View style={this.viewStyle()}>
<TitleText label="Top" />
</View>
<View style={this.viewStyle()}>
<TitleText label="Home" />
</View>
<View style={this.viewStyle()}>
<TitleText label="Bottom" />
</View>
</Swiper>
<View style={this.viewStyle()}>
<TitleText label="Right" />
</View>
</Swiper>
)
}
}
export default Home
Unfortunately, vertical navigation is not supported on Android yet.
I’ve used the react-native-swiper component in a previous project and loved it! I figured I can tweak it a little bit to meet my requirements.
You can clearly see that I have seperated my screens and navigation files in a separate folder.
This is my root navigator file:
import {
createStackNavigator
} from 'react-navigation';
import Login from '../screens/Login';
import SplashScreen from '../screens/SplashScreen';
import HomeNavigation from './HomeNavigation';
export default RootNavigation = createStackNavigator({
// Settings:UserProfile,
SplashScreen: SplashScreen,
Login: Login,
DrawerNavigation: HomeNavigation
}, {
headerMode: 'none',
});
This is my root navigator file:
Here i declare all my screens and link it to the root navigator
import React, {
Component
} from 'react';
import {
Text,
View,
Image,
SafeAreaView,
ScrollView,
Dimensions,
AsyncStorage,
ImageBackground,
TouchableOpacity,
Platform
} from 'react-native';
import {
Icon
} from 'native-base';
import {
createStackNavigator,
createDrawerNavigator,
DrawerItems,
createSwitchNavigator,
Header
} from 'react-navigation';
import AppDataStorage from '../helper/AppDataStorage';
import Colors from '../config/Colors';
import {
RNToasty
} from 'react-native-toasty';
import Home from '../screens/Home';
import Contact from '../screens/Contact';
import AboutUs from '../screens/AboutUs';
import Search from '../screens/Search';
import MyProfile from '../screens/MyProfile';
import {
getStatusBarHeight
} from 'react-native-status-bar-height';
var width = Dimensions.get('window').width;
var height = Dimensions.get('window').height;
var drawerWidth = ((width * 0.75) > 350) ? 350 : (width * 0.75);
const ImageHeader = props => ( <
View style = {
{
backgroundColor: '#eee'
}
} >
<
LinearGradien style = {
{
height: '100%',
width: '100%'
}
}
start = {
{
x: 0,
y: 1
}
}
end = {
{
x: 1,
y: 0
}
}
colors = {
['#4c669f', '#3b5998', '#192f6a']
}
/> <
Header { ...props
}
style = {
{
backgroundColor: 'transparent'
}
}
/> < /
View >
);
const headerOptions = (props) => {
return {
// header: (props) => <ImageHeader {...props} />,
headerStyle: {
backgroundColor: Colors.transparent,
paddingTop: Platform.OS === 'ios' ? 0 : getStatusBarHeight(),
height: Header.HEIGHT + (Platform.OS === 'ios' ? 0 : getStatusBarHeight()),
},
headerTintColor: Colors.white,
headerTitleStyle: {
fontWeight: 'bold',
},
headerMode: 'float',
headerLeft: < Icon
onPress = {
() => props.navigation.openDrawer()
}
name = "menu"
type = 'MaterialIcons'
style = {
{
color: 'white',
marginLeft: 10
}
}
/>,
}
};
class homeDrawerComponent extends Component {
constructor(props) {
super(props);
this.state = {
user: null
};
}
async componentDidMount() {
let user = await AppDataStorage.getUser();
console.log("user drawer", user);
await this.setState({
user: user
});
}
render() {
const email = this.state.user ? this.state.user.email : '';
const name = this.state.user ? (this.state.user.firstName + ' ' + this.state.user.lastName) : '';
return ( <
View style = {
{
flex: 1
}
} >
<
ImageBackground resizeMode = 'cover'
source = {
require('../assets/images/Cover.png')
}
style = {
{
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
height: 200,
marginBottom: 32
}
} >
<
View style = {
{
width: 80,
height: 80,
backgroundColor: Colors.white,
marginTop: 40,
borderRadius: 40
}
} >
<
Image source = {
require('../assets/images/drawer-logo.png')
}
style = {
{
width: 80,
height: 80,
}
}
resizeMode = 'contain' / >
<
/View> <
Text style = {
{
marginTop: 10,
color: Colors.white,
fontSize: 14,
}
} > {
name
} <
/Text> <
Text style = {
{
color: Colors.white,
fontSize: 14,
}
} > {
email
} <
/Text> < /
ImageBackground > <
ScrollView showsVerticalScrollIndicator = {
false
} >
<
DrawerItems activeBackgroundColor = '#1a9eae1a'
activeTintColor = {
Colors.secondaryColor
}
inactiveTintColor = {
Colors.primaryColor
}
labelStyle = {
{
color: Colors.text2
}
} { ...this.props
}
/> <
TouchableOpacity onPress = {
() => {
AsyncStorage.clear();
OneSignal.setSubscription(false);
RNToasty.Info({
title: 'You have been logged out.'
})
this.props.navigation.navigate('SplashScreen');
}
} >
<
View style = {
{
padding: 16,
flexDirection: 'row',
alignItems: 'center'
}
} >
<
Icon
type = "MaterialCommunityIcons"
name = "logout"
style = {
{
fontSize: 24,
color: Colors.primaryColor,
fontWeight: 'bold'
}
}
/> <
Text style = {
{
fontSize: 14,
color: Colors.text2,
fontWeight: 'bold',
marginLeft: 32
}
} > Sign Out < /Text> < /
View > <
/TouchableOpacity> < /
ScrollView > {
/* <TouchableOpacity onPress={() => {
AsyncStorage.clear();
RNToasty.Info({ title: 'You have been logged out.' })
this.props.navigation.navigate('SplashScreen');
}}> */
} {
/* <Icon
onPress={() => {
AsyncStorage.clear();
OneSignal.setSubscription(false);
RNToasty.Info({ title: 'You have been logged out.' })
this.props.navigation.navigate('SplashScreen');
}}
type="MaterialCommunityIcons"
name="logout"
style={{ color: Colors.secondaryColor, padding: 16, textAlign: 'left', marginBottom: 20, fontWeight: 'bold' }}> Logout</Icon> */
} <
/View>
)
}
}
const HomeStack = createStackNavigator({
Home: Home,
Search: Search,
Contact: Contact,
}, {
defaultNavigationOptions: headerOptions
});
HomeStack.navigationOptions = ({
navigation
}) => {
let drawerLockMode = 'unlocked';
if (navigation.state.index > 2) {
drawerLockMode = 'locked-closed';
}
return {
drawerLockMode,
};
};
const AboutUsStack = createStackNavigator({
AboutUs: AboutUs,
}, {
defaultNavigationOptions: headerOptions
});
export default HomeNavigation = createDrawerNavigator({
Home: {
screen: HomeStack,
navigationOptions: {
drawerLabel: 'Home',
drawerIcon: ({
tintColor
}) => ( <
Icon type = "FontAwesome5"
name = "home"
style = {
{
fontSize: 20,
color: tintColor
}
}
/>
)
}
},
{
header: null,
contentComponent: homeDrawerComponent,
// drawerWidth
},
);
You're good to go!
Hope this helps.