Use componentDidUpdate to dynamically change selected date in React component - react-native

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);

Related

State not updating as expected useing React-Native, useState, and setInterval

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
}
});

How can I get the value from custom radio button component in parent class

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;

React Native Animated lagging - Prevent Re-Rendering by function call

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;

React Native Search Dropdown

I'm working on React native app. I'm looking for a searchable dropdown which I need to implement in many places.
Below see below video for reference:
Sample Video
I have implemented below third parties but they are not same as I need:
https://www.npmjs.com/package/react-native-searchable-dropdown
https://www.npmjs.com/package/react-native-searchable-selectbox
https://github.com/toystars/react-native-multiple-select
I tried implementing something similar a while ago and at the time I dropped the idea of having a drop down as it was inconsistent on both platforms & I could not find a perfect solution. I cannot see your video but I think I know where you're going with this.
Here is my advice:
I would create a separate screen that opens on the tap on this component that would be a 'dropdown', and in there create a searchable/filtrable list. You could try doing that using this: https://www.npmjs.com/package/searchable-flatlist, or create your own flatlist, which is super easy and allows for more customization!
EDIT:
If you don't want a separate screen use this: https://www.npmjs.com/package/react-native-searchable-dropdown
try implementing one :
const sports = ["Badminton","Cricket","Chess","Kho-Kho","Kabbadi","Hockey","Boxing","Football","Basketball","Volleyball","Tennis","Table Tennis"];
predict(){
let q = this.state.query;
let arr = sports.filter(ele => ele.toLowerCase().indexOf(q.toLowerCase()) != -1).splice(0,5);
this.setState((prev = this.state)=>{
let obj={};
Object.assign(obj,prev);
obj.predictions.splice(0,obj.predictions.length);
arr.forEach(ele=>{
obj.predictions.push({key : ele});
});
return obj;
});
}
<TouchableWithoutFeedback onPress={()=>{this.setState({done : true})}}>
<ScrollView
keyboardShouldPersistTaps='handled'
contentContainerStyle={style.content}
>
<View>
<TextInput
onChangeText={(text)=>{
this.setState({query : text , done : false});
this.predict();
}}
placeholder="What do you want to play ?"
placeholderTextColor="#A6A4A4"
value = {this.state.query}
onSubmitEditing = {()=>{this.setState({done : true})}}
underlineColorAndroid = "#0098fd"
></TextInput>
<TouchableOpacity onPress={()=>{this.filteredEvents()}}><Icon name="search" color = "#0098fd" size = {20}></Icon></TouchableOpacity>
</View>
{
this.state.predictions.length != 0 && !this.state.done &&
<FlatList
style={styles.predictor_view}
data={this.state.predictions}
extraData = {this.state}
renderItem = {
({item})=>(
<TouchableOpacity
style={styles.predictions}
onPress={()=>{
console.log('ok');
this.setState({query : item.key,done : true});
console.log(this.state);
}}>
<Text>{item.key}</Text>
</TouchableOpacity>
)
}
/>
}
</ScrollView>
</TouchableWithoutFeedback>
I have used react-native-autocomplete-input
I have written a component to help in the dropdown:
customDropDownComponent.js
/*This is an example of AutoComplete Input/ AutoSuggestion Input*/
import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, View, Button, ScrollView } from 'react-native';
//import all the components we are going to use.
import Autocomplete from 'react-native-autocomplete-input'
//import Autocomplete component
class CustomDropDownComponent extends Component {
constructor(props) {
super(props);
//Initialization of state
//films will contain the array of suggestion
//query will have the input from the autocomplete input
this.state = {
query: '',
data: [],
dataDuplicate:[],
itemSelected: false
};
}
componentDidMount() {
//Loading all data
this.loadData()
}
findElement(query, text, itemSelected) {
//method called everytime when we change the value of the inputquery === '' ||
if (itemSelected === true||query==='') {
//if the query is null or an item is selected then return blank
return [];
}
//making a case insensitive regular expression to get similar value from the data json
const regex = new RegExp(`${query.trim()}`, 'i');
//return the filtered data array according the query from the input
var newList = [];
var result = this.state.IATADup.filter(data => {
if (data.id.search(regex) === 0) {
newList.push(data);
return false;
} else {
return data.id.search(regex) >= 0;
}
});
result = newList.concat(result);
this.props.adjustMargin(1, result.length);//expadnding space in page to make room for dropdown
this.setState({ data: result, query: text, itemSelected: itemSelected });
}
loadData = () => {
var dataToLoad = Commondata.aircraftDetail
dataToLoad.sort(function (a, b) {
if (a.id > b.id) {
return 1;
}
if (b.id > a.id) {
return -1;
}
return 0;
});
this.setState({
dataDuplicate: dataToLoad
})
}
render() {
const { query } = this.state;
const data = this.state.data;
const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim();
var inputContainerStyle = styles.inputContainerStyle;
if (this.props.destinationBorder === "#FF0000") {
inputContainerStyle = styles.inputContainerRedStyle;
}
return (
<View style={styles.container} >
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
flatListProps={{ nestedScrollEnabled: true }}
containerStyle={styles.autocompleteContainer}
listStyle={styles.listStyle}
inputContainerStyle={inputContainerStyle}
data={data}
keyExtractor={(item, i) => { return i }
defaultValue={query}
onChangeText={text => {
//handle input
if (text.trim() === "") this.props.adjustMarginBack();//adjust margin to normal in case of empty searrch element
this.findElement(text, text, false);//search for element
}}
placeholder={en.pick_one}
renderItem={({ item }) => (
//you can change the view you want to show in suggestion from here
<TouchableOpacity onPress={() => {
this.props.adjustMarginBack()
this.setState({ query: item.id, itemSelected: true, data: [] });
}}>
<Text style={styles.itemText}>
{item.id}
</Text>
<Text style={styles.itemSubText}>
{item.name}
</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#F5FCFF'
},
autocompleteContainer: {
backgroundColor: '#ffffff',
borderWidth: 0,
},
inputContainerStyle: {
borderWidth: 0.5, borderColor: '#D9D9D9', padding: '1.5%'
},
inputContainerRedStyle: {
borderWidth: 0.5, borderColor: '#FF0000', padding: '1.5%'
},
descriptionContainer: {
flex: 1,
justifyContent: 'center',
padding: '5%'
},
itemText: {
fontSize: 15,
paddingTop: 5,
paddingBottom: 5,
margin: 2,
},
itemSubText: {
fontSize: 10,
paddingTop: 5,
paddingBottom: 5,
margin: 2,
marginLeft: 10
},
infoText: {
textAlign: 'center',
fontSize: 16,
},
listStyle: {
height: 100,
position: "relative",
zIndex: 999
}
});
export default CustomComponent;
Now in the display screen:
app.js
import React, { Component } from 'react';
import { View, Text, ScrollView } from 'react-native';
import CustomDropDownComponent from './CustomDropDownComponent.js'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<View>
<ScrollView
nestedScrollEnabled={true}
keyboardShouldPersistTaps={'handled'}>
<CustomDropDownComponent /*Handle all inputs and margin resets as props *//>
</ScrollView>
</View>
);
}
}

i make a side menu,but PanResponder not work on phone

i make a side menu,it's ok on genymotion,but not work on my phone. it's response is delay more than 10s and most times not response on phone。please help me !
At the beginning ,I think it's flow reason:
1、position: 'absolute'
2、PanResponder wrapper a touchable
i had try clean this,but it's also not work。maybe its a bug ,are you ?
enter image description here
import React, {Component} from 'react'
import {
View,
Text,
StyleSheet,
ScrollView,
Alert,
PanResponder,
TouchableOpacity
} from 'react-native'
import {get_pointBaikeCate} from 'api'
import {Touchable} from 'basic'
export default class PointBaike extends Component {
static navigationOptions = {
title: '穴位百科',
header: null
}
constructor(props) {
super(props);
this.state = {
info: []
}
this.scrollY = []
}
async getInfo() {
let res = await get_pointBaikeCate();
console.log(res)
// let info = [];
// res.data.map((r)=> {
// r.content.map(c=> {
// let flag = info.find((item)=> {
// return item.jl == c.jl
// })
// if (flag) {
// flag.xw += `,${c.xw}`
// } else {
// info.push({
// jl: c.jl,
// xw: c.xw
// })
// }
// })
// });
//
// // 对info的xw进行过滤
// info = info.map((item)=> {
// let xw = item.xw.split(/[,|]/)
// item.xw = [...new Set(xw)].filter((x)=> {
// return x
// })
// return item
// });
this.setState({
info: res.data
})
}
goDetail(item) {
let {navigate} = this.props.navigation
console.log(item)
navigate('PointBaikeDetail', {
item
})
}
componentWillMount() {
this.getInfo()
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder.bind(this),
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture.bind(this),
onMoveShouldSetPanResponder: this._handlerMoveShouldSetPanResponder.bind(this),
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture.bind(this),
onPanResponderTerminationRequest: this._handleMoveShouldSetPanResponderCapture,
onPanResponderMove: this._handlePanResponderMove.bind(this),
});
}
_handlerMoveShouldSetPanResponder(evt, gestureState){
if (gestureState.dx != 0 && gestureState.dy == 0) {
return true;
}
return false;
}
_handleMoveShouldSetPanResponderCapture(evt, gestureState) {
return gestureState.dx != 0 && gestureState.dy != 0
}
_handleStartShouldSetPanResponderCapture(evt, gestureState) {
return gestureState.dx != 0 && gestureState.dy != 0;
}
_handleMoveShouldSetPanResponderCapture(evt, gestureState) {
return gestureState.dx != 0 && gestureState.dy != 0;
}
_scrollTo(index) {
// Alert.alert('索引', `${index}`)
this._scrollView.scrollTo({y: this.scrollY[index]})
}
_handleStartShouldSetPanResponder() {
return true
}
_handlePanResponderMove(e, gestureState) {
// console.log('滑动', e.nativeEvent.pageY)
// 计算手指在那个元素上,得出index,然后根据index设置scrollTop
let y = e.nativeEvent.pageY - 100
let index = Math.ceil(y / 20) - 1
console.log(index, this)
this._scrollView.scrollTo({y: this.scrollY[index]})
}
_onLayout({nativeEvent}) {
this.scrollY.push(nativeEvent.layout.y)
}
render() {
let {info} = this.state
return (
<View style={styles.wrapper}>
<ScrollView style={{flex: 1, height: 300}}
showsVerticalScrollIndicator={false}
ref={(e)=> {
this._scrollView = e
}}
>
{
info.map((item, i)=> {
return (
<View key={i} style={styles.lists}
onLayout={this._onLayout.bind(this)}
>
<View>
<Text style={styles.title}>{item.title}</Text>
</View>
<View style={styles.listBox}>
{
item.list.map((x, xi)=> {
return (
<Touchable key={xi}
onPress={this.goDetail.bind(this, x)}
>
<View style={styles.list}>
<Text
style={styles.text}>{x.title}</Text>
</View>
</Touchable>
)
})
}
</View>
</View>
)
})
}
<View style={{height: 100}}></View>
</ScrollView>
<View style={styles.sideMenu}
{...this._panResponder.panHandlers}
>
{
info.map((item, i)=> {
return (
<TouchableOpacity key={i} onPress={this._scrollTo.bind(this, i)}>
<View>
<Text
style={styles.sideText}>{item.title.charAt(3) || item.title.charAt(0)}</Text>
</View>
</TouchableOpacity>
)
})
}
</View>
</View>
)
}
}
const styles = StyleSheet.create({
wrapper: {
paddingLeft: 6,
paddingRight: 6,
paddingTop: 10,
paddingBottom: 10,
flex: 1,
backgroundColor: '#fff'
},
sideText: {
width: 25,
height: 20,
lineHeight: 20,
textAlign: 'center'
},
sideMenu: {
position: 'absolute',
right: 0,
top: 100,
zIndex: 100,
backgroundColor: '#eee'
},
lists: {},
listBox: {
flexDirection: 'row',
flexWrap: 'wrap'
},
list: {
width: (WinWidth - 20) / 5,
},
title: {
fontSize: 18,
color: DEFAULT_COLOR,
paddingTop: 14,
paddingBottom: 10
},
text: {
flex: 1,
textAlign: 'center',
paddingTop: 10,
paddingBottom: 10,
}
})
you can try this
componentWillMount() {
this.getInfo()
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture.bind(this),
onMoveShouldSetPanResponder: this._handlerMoveShouldSetPanResponder.bind(this),
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture.bind(this),
onPanResponderTerminationRequest: this._handleMoveShouldSetPanResponderCapture,
onPanResponderMove: this._handlePanResponderMove.bind(this),
});
}