Keyboard listener is running more then once - react-native

Hey I'm trying to create an event that will fire when the keyboard shows up but the function is firing more then once, I don't know why ..
import React, { Component } from 'react';
import { Keyboard, Alert, View, TextInput } from 'react-native';
export default class App extends Component {
constructor(props: any) {
super(props);
this.kbDidShowListener = Keyboard.addListener('keyboardDidShow', () => Alert.alert('keyboard is up'));
}
componentWillUnmount() {
this.kbDidShowListener.remove();
}
render() {
return (
<View style={{ marginTop: 30 }}>
<TextInput />
</View>
);
}
}
here is an expo for the example (you will see the alert more then once)
https://snack.expo.io/H1DHaIdgM
p.s I'm working on Android.
thanks!

The render function does not run only once. Usually refreshes multiple times too, while calculating the state and props. That could explain the issue.
If you want to be sure, try adding a console too inside the render method, to see if the numbers match.
Actually, another thing I am thinking. Try moving the code to the componentWillMount or componentDidMount
componentDidMount(){
this.kbDidShowListener = Keyboard.addListener('keyboardDidShow', () => Alert.alert('keyboard is up'));
}

Related

React native How to execute function every time when i open page

I need to send request every time when i open page. Currently when i access page first time after load the app everything is ok, but if i go to another page and back after that request is not send it again.
You have to add focus listener so when you go back, It will refresh the data like
import * as React from 'react';
import { View } from 'react-native';
function AppScreen({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// The screen is focused
// Call any action and update data
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [navigation]);
return <View />;
}
source : https://reactnavigation.org/docs/function-after-focusing-screen/
Here you go, example for a class based and functional based component to run something on every load of the screen.
import React, { useEffect } from "react";
import {View} from 'react-native'
//Functional Component
const App = () =>
{
useEffect(() =>
{
myAction();
}, [])
return (
<View>
</View>
);
}
//Class based Component
class App extends Component
{
componentDidMount()
{
this.myAction();
}
render()
{
return(
<View>
</View>
)
}
}

Navigation.goBack() with an API call in React-Native

In my application, I have few cases where navigation.goBack() cannot be used. I use react-navigation for navigation. When i'm in the detail screen, When I go back, I want to send an API call to get the latest records to the parent screen. So I used, navigation.navigate() instead of navigation.goBack(); But, this makes my app slow if I navigate and navigate back few times. It gets very slow if I do this few more times. What is the reason behind this? How the navigation.navigate() differs from navigation.goBack()?
What is the preferred way of handling this kind of scenario?
is there a way to pass param from navigate.goback() and parent can listen to the params and update its state?
You can pass a callback function as parameter (as mentioned in other answers).
Here is a more clear example, when you navigate from A to B and you want B to communicate information back to A you can pass a callback (here onSelect):
ViewA.js
import React from "react";
import { Button, Text, View } from "react-native";
class ViewA extends React.Component {
state = { selected: false };
onSelect = data => {
this.setState(data);
};
onPress = () => {
this.props.navigate("ViewB", { onSelect: this.onSelect });
};
render() {
return (
<View>
<Text>{this.state.selected ? "Selected" : "Not Selected"}</Text>
<Button title="Next" onPress={this.onPress} />
</View>
);
}
}
ViewB.js
import React from "react";
import { Button } from "react-native";
class ViewB extends React.Component {
goBack() {
const { navigation } = this.props;
navigation.goBack();
navigation.state.params.onSelect({ selected: true });
}
render() {
return <Button title="back" onPress={this.goBack} />;
}
}
Hats off for debrice - Refer to https://github.com/react-navigation/react-navigation/issues/288#issuecomment-315684617

React Native - OnPress button not working

I have a button with a onPress event i am trying to simply console log that the button was successfully pressed.
The button is within my template file i have extracted this portion of code to a separate file, ive done the same for my tyles.
The add() function associated with the press does not fire when i press the add button, so the console.log does not show.
But the console.log does does show on initial load of the screen ( i dont know why this is )
items.screen.js
import React, { Component } from 'react';
import { StyleSheet, Text, View, TextInput } from 'react-native';
import style from './items.style'
import template from './items.template';
export default class ItemsScreen extends Component {
static navigationOptions = {
title: "Items"
}
constructor(props) {
super(props);
this.state = { text: '' };
}
add() {
console.log("pressed add button");
}
render() {
return template(this, style);
}
}
items.template.js
import React, { Component } from 'react';
import { Text, View, TextInput, Button } from 'react-native';
import { style } from './items.style'
export default (template, style) => {
return (
<View style={style.container}>
<TextInput
style={{ width: 300 }}
value={template.state.text}
onChangeText={(text) => template.setState({ text })}
/>
<Button
onPress={template.add()}
title="Add"
/>
</View>
);
}
You are not assigning template.add to the on press event, instead your code is executing the add function and attempting to assign the result of that to the onPress event.
I find the following two options to be cleaner than the other suggestions:
Create a local onPress Handler (in items.template)
onPress = () => {
template.add();
}
and then
onPress={this.onPress} //note the lack of parentheses `()`
or
create an inline function
onPress={() => template.add()}
You should pass function in attribute,not function call, so it should be
<Button
onPress={template.add.bind(template)}
title="Add"
/>

react native touchable highlight and touchable native feedback for making my menu items

I am trying to implement a basic drawer, but I am confused by the documentation.
There is a TouchableHighlight, and there is a TouchableNativeFeedback.
TouchableNativeFeedback is Android only. So, how can I best handle my MenuItem component so it takes care of both Android and IOS?
Here's what I've got so far, and I'm not sure how to handle the different platform specific touchable stuff here:
import React, { Component, PropTypes } from 'react';
import { TouchableHighlight, TouchableNativeFeedback, Platform, View, Text } from 'react-native';
export class MenuItem extends Component {
handleAndroid() {
}
handleIOS() {
}
renderMenuItem() {
return (
<View style={styles.container}>
<Text style={styles.text}>
{this.props.text}
</Text>
</View>
);
}
render() {
return (
);
}
}
On Android, do I have to use TouchableNativeFeedback only?
Like you said, TouchableNativeFeedback is Android only therefore it won't work for iOS. If you want a solution that works for both, use TouchableHighlight and style it accordingly. For example, its underlayColor prop allows you to set "the color of the underlay that will show through when the touch is active."
EDIT:
If you want to use TouchableNativeFeedback for Android and TouchableHighlight for iOS, you should do something like the following:
import { Platform } from 'react-native'
...
render() {
if (Platform.OS === 'android') {
return (
<TouchableNativeFeedback>
...
</TouchableNativeFeedback>
)
} else {
return (
<TouchableHighlight>
...
</TouchableHighlight>
)
}
}
EDIT 2:
Alternatively, you can create a custom component for each platform and use file extensions. For example:
MyButton.ios.js
MyButton.android.js
Put these two in the same folder and then use MyButton as a regular component:
import MyButton from '../path/to/component/MyButton'
...
render() {
<MyButton/>
}
This is quite neat when you want to use this component in multiple places because you don't fill your code with if-else blocks.
Here you can read more about Platform Specific Code.
You can solve this in a more elegant manner like this:
render() {
let TouchablePlatformSpecific = Platform.OS === 'ios' ?
TouchableOpacity :
TouchableNativeFeedback;
let touchableStyle = Platform.OS === 'ios' ?
styles.iosTouchable :
styles.androidTouchable
return(
<TouchablePlatformSpecific style={touchableStyle}>
(...)
</TouchablePlatformSpecific>
);
}
I've been using this successfully, and I fail to see a reason it would not work, it looks good to me.
A better implementation would be to have dumb component that produces generic Touchable that works depending upon the platform and handle onPress events.
Touchable Component:
import { Platform } from "react-native"
import { TouchableNativeFeedback, TouchableOpacity } from "react-native"
import React from "react"
export const Touchable = (props: any) => {
return Platform.OS === 'android'
? <TouchableNativeFeedback onPress={props.onPress}>{props.children}</TouchableNativeFeedback>
: <TouchableOpacity onPress={props.onPress}>{props.children}</TouchableOpacity>
}
Usage:
import { Touchable } from '../path/to/touchable.component';
...
render() {
<Touchable onPress={() => this.handleClick()}>
.....
</Touchable>
}
Thanks to #Giorgos' implementation which formed the basis for this answer.
Or you could create a custom component like this:
import { TouchableNativeFeedback, TouchableOpacity } from 'react-native'
...
const Touchable = (props) => {
return Platform.OS === 'android'
? <TouchableNativeFeedback>{props.children}</TouchableNativeFeedback>
: <TouchableOpacity>{props.children}</TouchableOpacity>
}
And then use it like this:
<Touchable>
<Card>
<Text>Click me!</Text>
</Card>
</Touchable>
I also have nearly the same requirements and I ended up using this library react-native-haptic-feedback.
Keep in mind that haptic feedback is available only on some latest android devices and in iOS above iPhone 6s. For devices without haptic motor, there will be vibration. Here is a sample code snippet:
import ReactNativeHapticFeedback from "react-native-haptic-feedback";
const options = {
enableVibrateFallback: true,
ignoreAndroidSystemSettings: false
};
ReactNativeHapticFeedback.trigger("impactMedium", options);
In your case, it will work directly with the button's onPress method such as:
<TouchableOpacity onPress={()=>{ReactNativeHapticFeedback.trigger("impactMedium", options);}} />
Note: I found that the most versatile type which is supported by maximum devices is impactMedium.
Based on #Nithish JV and Giorgos Karyofyllis:
import React from "react"
import { Platform } from "react-native"
import { TouchableNativeFeedback, TouchableOpacity } from "react-native"
export const TouchableFeedback = (props: any) => {
const { children, ...rest } = props;
return Platform.OS === 'android'
? <TouchableNativeFeedback {...rest}>{children}</TouchableNativeFeedback>
: <TouchableOpacity {...rest}>{children}</TouchableOpacity>
}
The best elegant way to do it
import { TouchableNativeFeedback, TouchableOpacity } from 'react-native'
in render:
TouchableButton = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity
return (
<TouchableButton>{'bla bla'}</TouchableButton>
)
Since the operating system doesn't change magically during runtime, I always use this little snippet:
import { Component } from "react"
import {
Platform,
TouchableNativeFeedback,
TouchableOpacity,
} from "react-native"
// The operating system doesn't magically change,
// so we can call the function directly to get the correct component
export default ((): typeof Component => {
if (Platform.OS === "android") {
return TouchableNativeFeedback
}
return TouchableOpacity // Choose whatever touchable you like
})()
It's basically an IIFE that selects the correct touchable for the operating system.

React Native Android lag during animation

I experience the lag during transition animation when Navigator goes to below scene ShiftEdit. Animation starts immediately but it stops for a millisecond. InteractionManager is used to postpone rendering of four picker components. Every picker component has list of items that is built from an array. There is lots of items. Is it possible that this is calculated even when picker component isn't rendered yet in ShiftEdit and this is the reason of the lag? Could you help me please?
'use strict'
import React, {View, Text, StyleSheet, InteractionManager, TouchableOpacity} from 'react-native';
import { connect } from 'react-redux';
import Spinner from 'react-native-spinkit';
import StyleCommon from '../styles';
import TimePicker from '../components/time-picker';
import ColorPicker from '../components/color-picker';
import LabelPicker from '../components/label-picker';
class ShiftEdit extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: false,
shiftId: '',
startHour: '',
endHour: '',
color: '',
}
}
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.setState({isReady: true});
});
}
onChangeItem = (label, val) => {
let data = {};
data[label] = val;
this.setState(data);
}
renderPlaceholder() {
return (
<View style={styles.container}>
<Text>Loading...</Text>
</View>
)
}
render() {
if (!this.state.isReady) {
return this.renderPlaceholder();
}
return (
<View style={{flex:1, flexDirection: 'column'}}>
<TimePicker label={'Start hour'} value={this.state.startHour} onChange={this.onChangeItem.bind(this, 'startHour')} />
<TimePicker label={'End hour'} value={this.state.endHour} onChange={this.onChangeItem.bind(this, 'endHour')} />
<ColorPicker label={'Color'} value={this.state.color} onChange={this.onChangeItem.bind(this, 'color')} />
<LabelPicker label={'Shift ID'} value={this.state.shiftId} onChange={this.onChangeItem.bind(this, 'shiftId')} />
</View>
)
}
};
I tried to control animation registration as Chris suggested but it still the same:
onPress = () => {
let handle = InteractionManager.createInteractionHandle();
this.props.navigator.push({component: 'shiftedit'});
InteractionManager.clearInteractionHandle(handle);
}
Actually this is the only solution that works for me now:
componentDidMount() {
// InteractionManager.runAfterInteractions(() => {
// this.setState({isReady: true});
// })
setTimeout(() => {
this.setState({isReady: true});
}, 75);
}
but I'd rather use InteractionManager...
Here's a wild guess as I've no experience with InteractionManager directly. But after looking over the Interaction Manager Docs I noticed that there's a way to register animations. So, my guess is that the Navigator's animations haven't been properly registered. So maybe try something like this...
var handle = InteractionManager.createInteractionHandle();
// run your navigator.push() here... (`runAfterInteractions` tasks are queued)
// later, on animation completion:
InteractionManager.clearInteractionHandle(handle);
// queued tasks run if all handles were cleared
Hope that helps!
Also, keep in mind that if you're running the React Native Debugger while testing your app, React Native animations will appear jittery on Android.
That's been my experience.
https://github.com/jhen0409/react-native-debugger