OnLongPress popup witha list of options react-native - javascript

I want to add a functionality to aa button where long press results in opening of a popup where user can choose from multiple options. I wanted to know if there is any component available that renders that prop because AFAIK onLongPress of TouchableHighlight(or Opacity)is non renderable. I understand I can change the state and display another view but I want to make the menu transparent so that clicking(or tapping) on the background results in disappearance of the menu.

The react-native-popover project looks like it could be an option for you. A note of caution is that I'm not sure how actively maintained it is at the moment. For example, the current version is 0.3.0 but only 0.2.0 is released to npm. To correct that in the interim, see this issue.
At a minimum, you can at least review this code as it accomplishes what I believe you're after. Here is a code sample extended off the project site that creates a popover component on a button with a transparent background. When the background is tapped, the popover closes.
import React from 'react';
import {
AppRegistry,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
import Popover from 'react-native-popover';
class MyApp extends React.Component {
constructor (props) {
super(props);
this.onLongPress = this.onLongPress.bind(this);
this.onClose = this.onClose.bind(this);
this.state = {
isVisible: false,
buttonRect: {}
}
}
onLongPress () {
this._button.measure((ox, oy, width, height, px, py) => {
this.setState({
isVisible: true,
buttonRect: {x: px, y: py, width: width, height: height}
});
});
}
onClose () {
this.setState({ isVisible: false });
}
render () {
return (
<View style={styles.container}>
<TouchableOpacity
ref={(component) => this._button = component}
style={styles.button}
onLongPress={this.onLongPress}>
<Text>Long Press Me</Text>
</TouchableOpacity>
<Popover
isVisible={this.state.isVisible}
fromRect={this.state.buttonRect}
onClose={this.onClose}
backgroundStyle={styles.popoverBackground}>
<Text>I'm the content of this popover!</Text>
</Popover>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
button: {
backgroundColor: '#ddd',
padding: 20
},
popoverBackground: {
backgroundColor: 'rgba(0,0,0,0)'
}
});
AppRegistry.registerComponent('MyApp', () => MyApp);

Related

React Native - Animate width shrink

In the header of my React Native app, I have a conditional icon and a Searchbar.
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
headerTitle: (
<View
style={{
flex: 1,
backgroundColor: Platform.OS === 'ios' ? '#e54b4d' : '',
alignItems: 'center',
flexDirection: 'row',
paddingHorizontal: 10,
height: StatusBar.currentHeight,
}}>
{params.isIconTriggered && <Icon name="chevron-left" size={28} />}
<SearchBar
round
platform={'default'}
placeholder="Search"
containerStyle={{
flex: 1,
backgroundColor: 'transparent',
}}
/>
</View>
),
headerStyle: {
backgroundColor: '#e54b4d',
},
};
};
Normally the Searchbar will take the full width of the header which is what I want. If the condition isIconTriggered is true, an icon will appear in front of the Searchbar and the width of the SearchBar will shrink enough so that the icon is visible next to it.
However, there is no transition or animation when this happens and it does not feel nor look nice. I would like to add an animation to the Searchbar so the width shrinks gradually and smoothly when the condition is triggered and the icon appears.
Is that possible to achieve and how can I achieve this?
Try to learn Animated API of react native.
Here is how i done it with button trigger.
import React, {Component} from 'react';
import {StyleSheet, View, TextInput , Button, SafeAreaView, Animated} from 'react-native';
import FA from 'react-native-vector-icons/FontAwesome5'
const AnimatedIcon = Animated.createAnimatedComponent(FA)
// make your icon animatable using createAnimatedComponent method
export default class Application extends Component {
animVal = new Animated.Value(0);
// initialize animated value to use for animation, whereas initial value is zero
interpolateIcon = this.animVal.interpolate({inputRange:[0,1], outputRange:[0,1]})
interpolateBar = this.animVal.interpolate({inputRange:[0,1],outputRange:['100%','90%']})
// initialize interpolation to control the output value that will be passed on styles
// since we will animate both search bar and icon. we need to initialize both
// on icon we will animate the scale whereas outputRange starts at 0 end in 1
// on search bar we will animate width. whereas outputRange starts at 100% end in 90%
animatedTransition = Animated.spring(this.animVal,{toValue:1})
// we use spring to make the animation bouncy . and it will animate to Value 1
clickAnimate = () => {
this.animatedTransition.start()
}
// button trigger for animation
//Components that will use on Animation must be Animated eg. Animted.View
render() {
return (
<SafeAreaView>
<View style={styles.container}>
<View style={styles.search}>
{/* our icon */}
<Animated.View style={{width: this.interpolateBar}}>
<TextInput placeholder='search here' style={styles.input}/>
</Animated.View>
<AnimatedIcon name='search' size={28} style={{paddingLeft: 10,paddingRight:10, transform:[{scale: this.interpolateIcon}]}}/>
</View>
<Button title='animate icon' onPress={this.clickAnimate}/>
</View>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor:'#F79D42',
// flex: 1,
height:'100%',
paddingTop:20,
flexDirection: 'column',
// justifyContent: 'center',
alignItems:'center'
},
input:{
width: '100%',
height:40,
backgroundColor:'gray',
textAlign:'center'
},
search:{
flexDirection:'row-reverse',
width:'90%',
height:40,
alignItems:'center'
}
});
Solution using react-native-elements SearchBar component.
Wrapped the SearchBar Component inside Animated.View.
to explicitly animate the search bar
Like This:
<Animated.View style={{width: this.interpolateBar}}>
<SearchBar
placeholder="Type Here..."
containerStyle={{width: '100%'}}
/>
</Animated.View>
You can achieve this using Animated API of React Native.
You can check this tutorial for an overview of changing the size of elements with animation.
React-Native-Animatable is super cool!
Try this one out:
Create A custom animation object
import * as Animatable from 'react-native-animatable';
Animatable.initializeRegistryWithDefinitions({
const myAnimation = {
from: {
width: 200
},
to: {
width: 100
}
}
})
Use is as Animation value within a view or as a reference within a function call.
Within a view:
<Animatable.View useNativeDriver animation={myAnimation}/>
As a reference variable:
<Animatable.View useNativeDriver ref={ref=>(this.testAnimation = ref)}/>
Method:
testMethod = () => {
this.testAnimation.myAnimation();
}

In React Native, is it possible to detect a swipe vs a tap in Pan Responder's onStartShouldSetPanResponder?

Right now, I return a true in my onStartShouldSetPanResponder, and as a result the PanResponder wants to handle taps AND pans. Is there any way to restrict it to just pans, as I want a TouchableHighlight to handle that? (I get that the Gesture Responder should handle both, but it seems weird that the "Pan" Responder handles taps)
Since the gesture is just starting, the dx/dy are 0 in onStartShouldSetPanResponder. Is there any way to detect if it's the start of a tap and return false, if so?
Or should I just detect whether it was a tap or pan in the OnPanResponderRelease?
I was able to accomplish this through the onMoveShouldSetPanResponder method as follows:
onMoveShouldSetPanResponder: (evt, gestureState) => {
return Math.abs(gestureState.dx) >= 1 || Math.abs(gestureState.dy) >= 1
}
If the x or y movement is greater than 1, return true. In order to then detect a tap, I had to wrap everything within my view containing the panHandlers with a touchable element. Here is a full working example:
import React, { Component } from 'react';
import { TouchableOpacity, Animated, PanResponder, Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
export default function App() {
return (
<View>
<CircleTapExample/>
</View>
);
}
class CircleTapExample extends Component {
constructor(props) {
super(props)
this.position = new Animated.ValueXY({ x: 0, y: 0 });
this.panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
return Math.abs(gestureState.dx) >= 1 || Math.abs(gestureState.dy) >= 1
},
onPanResponderMove: (evt, gestureState) => {
console.log("I was moved")
this.position.setValue({ x: gestureState.moveX, y: gestureState.moveY })
},
});
}
circleTapped() {
// Do something here when tapped
console.log("I was tapped")
}
render() {
return (
<Animated.View style={[styles.container, { ...this.position.getLayout() }]} {...this.panResponder.panHandlers}>
<TouchableOpacity onPress={() => this.circleTapped()} style={{ flex: 1 }}>
<View style={styles.circle} />
</TouchableOpacity>
</Animated.View>
)
}
}
const styles = StyleSheet.create({
container: {
width: 75,
height: 75,
bottom: 5,
left: 5,
position: 'absolute'
},
circle: {
width: 75,
height: 75,
borderRadius: 40,
backgroundColor: 'red'
}
});
<div data-snack-id="YskU-lxRe" data-snack-platform="web" data-snack-preview="true" data-snack-theme="light" style="overflow:hidden;background:#F9F9F9;border:1px solid var(--color-border);border-radius:4px;height:505px;width:100%"></div>
<script async src="https://snack.expo.dev/embed.js"></script>
The panResponder has two events :
onStartShouldSetPanResponder(Capture)
onMoveShouldSetPanResponder(Capture)
I've been able to solve this problem only by removing onStartShouldSetPanResponderCapture.

react native "attempt to set value to an immutable object"

I'm creating a draggable box. which I can drag anywhere on the screen but I'm getting this error which says that "You attempted to set the key _value on an object that is meant to be immutable and has been frozen". Can anyone tell me what am I doing wrong.
My Code:
import React, { Component } from 'react'
import {
AppRegistry,
StyleSheet,
Text,
Button,
ScrollView,
Dimensions,
PanResponder,
Animated,
View
} from 'react-native'
import { StackNavigator } from 'react-navigation'
export default class Home extends Component{
componentWillMount(){
this.animatedValue = new Animated.ValueXY();
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderGrant: (e, gestureState) => {
},
onPanResponderMove:Animated.event([
null,{dx: this.animatedValue.x , dy:this.animatedValue.y}
]),
onPanResponderRelease: (e, gestureState) => {
},
})
}
render(){
const animatedStyle = {
transform:this.animatedValue.getTranslateTransform()
}
return(
<View style={styles.container}>
<Animated.View style={[styles.box ,animatedStyle]} {...this.panResponder.panHandlers}>
<Text>Home</Text>
</Animated.View>
</View>
)
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
marginLeft: 10,
marginRight: 10,
alignItems: 'stretch',
justifyContent: 'center',
},
box:{
height:90,
width:90,
textAlign:'center'
}
});
In my case I got this error because I forgot to change the View into Animated.View.
Try this out. This will solve your issue.
You need to initialize animatedValue in state object to make it work.
constructor(props) {
super(props);
this.state = {
animatedValue: new Animated.ValueXY()
}
}
onPanResponderMove:Animated.event([
null,{dx: this.state.animatedValue.x , dy:this.state.animatedValue.y}
]),

undefined is not an object (evaluating 'state.cameraType')

I'm trying to get a React Native app set up following this post and then ported over to ES6. Here's the main page code:
"use strict";
import React, { Component } from 'react';
import Camera from 'react-native-camera';
import {
AppRegistry,
StyleSheet,
Text,
View,
TextInput,
TouchableHighlight,
} from 'react-native';
export default class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {cameraType: Camera.constants.Type.back};
}
render() {
return (
<Camera
ref="cam"
style={styles.container}
type={this.state.cameraType}>
<View style={styles.buttonBar}>
<TouchableHighlight style={styles.button} onPress={this._switchCamera.bind(this)}>
<Text style={styles.buttonText}>Flip</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.button} onPress={this._takePicture.bind(this)}>
<Text style={styles.buttonText}>Take</Text>
</TouchableHighlight>
</View>
</Camera>
);
}
_switchCamera: () => {
var state = this.state;
console.log(this.state);
state.cameraType = state.cameraType === Camera.constants.Type.back ? Camera.constants.Type.front : Camera.constants.Type.back;
this.setState(state);
}
_takePicture: () => {
console.log(this.refs);
this.refs.cam.capture(function(err, data) {
console.log(err, data);
});
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "transparent",
},
buttonBar: {
flexDirection: "row",
position: "absolute",
bottom: 25,
right: 0,
left: 0,
justifyContent: "center"
},
button: {
padding: 10,
color: "#FFFFFF",
borderWidth: 1,
borderColor: "#FFFFFF",
margin: 5
},
buttonText: {
color: "#FFFFFF"
}
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
I am getting a weird error saying both state.cameraType is undefined and this.refs is undefined. I have a hunch that something is not getting bound and my this is not pointing to right thing but I have tried using both arrow functions and explicitly binding. See comment below. Anyway, I am not sure why it's acting up, any ideas?
Very similar to this post except I have tried both the proposed solutions so I feel like something else is missing or I'm doing the binding wrong.
edit: Is there reason to be downvoted? Am I not being clear on something :o
It turns out manual binding is the way to go, just needed to reload the app. I did not have hot-swapping enabled. Alternatively, arrow functions should also work.
Another thing to point out, for iOS 10 and above, you'll have to set 2 things in the Info.plist file, Privacy - Camera Usage Description and Privacy - Photo Library Usage. It can be any string value.

Tap events for AlertIOS are passed to parent component below dialog (React-native)

Upgraded our React-native project to 0.20 and all of our AlertIOS dialogs are now broken. What's happening is the tap event is being passed below to the component, and the buttons on the AlertIOS are never touched.
Here's a simplified example and screenshot:
class TabOption extends Component {
constructor(props) {
super(props);
}
resetData = () => {
AlertIOS.alert('Reset Data',
'Are you sure you want to reset your data?',
[{text:'Yes', onPress: () => console.log('FIRE')},
{text:'No'}])
};
render() {
return(
<View style={{flex: 1,flexDirection: 'column',backgroundColor: 'white'}}>
<TouchableHighlight onPress={this.resetData} style={styles.tabOptionContainer}>
<Text> Disconnect </Text>
</TouchableHighlight>
</View>
);
};
}
const styles = StyleSheet.create({
tabOptionContainer: {
width:Dimensions.width,
height:50,
justifyContent:'center',
alignItems:'center',
backgroundColor:'white',
}
});
In the screenshot below, if I click either Yes or No the tap event is passed to the component beneath the alert. I can actually fully interact with the component, while no tap events are fired on the alert itself.
Note: it was a rather large upgrade. We bumped React from 0.14 to 0.20. Before the upgrade, everything was working just fine. What's going on here?
If I understand your issue, tapping on yes or no doesn't dismiss the alert box but instead is tapping the component beneath.
I used your example and tweaked it a little bit here.
The taps on the alert boxes work fine. Check it out.
Code
var React = require('react-native');
var {
AlertIOS,
AppRegistry,
Dimensions,
StyleSheet,
TouchableHighlight,
Component,
Text,
View,
} = React;
class StackOverflowApp extends Component {
constructor(props) {
super(props);
this.state = {
color: 'pink'
};
}
resetData = () => {
AlertIOS.alert('Reset Data',
'Are you sure you want to reset your data?',
[{text:'Yes', onPress: () => this.setState({color: 'red'})},
{text:'No', onPress: () => this.setState({color: 'blue'})}])
};
render() {
return(
<View style={{flex: 1,flexDirection: 'column',backgroundColor: this.state.color}}>
<TouchableHighlight onPress={this.resetData} style={styles.tabOptionContainer}>
<Text> Disconnect </Text>
</TouchableHighlight>
</View>
);
};
}
const styles = StyleSheet.create({
tabOptionContainer: {
width:Dimensions.width,
height:50,
justifyContent:'center',
alignItems:'center',
backgroundColor:'white',
}
});
AppRegistry.registerComponent('StackOverflowApp', () => StackOverflowApp);

Categories

Resources