Is there a way to use a keyboard only without any text input and get its values onChange?
I would like to show the keyboard only on button click event and render its typing values in a view without any input.
What would be a correct way of implementing this?
You can add a dummy TextInput and onPress button set focus on TextInput to show keyboard . Save state with "onChangeText" prop and show in a View
Complete Code
import React from "react";
import { View, Text, Button, TextInput } from "react-native";
export default class App extends React.Component {
state = { text: "" };
render() {
return (
<View style={{ flex: 1, marginTop: 50 }}>
<TextInput
style={{ height: 0, width: 0, borderWidth: 0 }}
onChangeText={text => this.setState({ text })}
ref={ref => {
this.textInput = ref;
}}
autoCapitalize="none"
autoCorrect={false}
autoFocus={false}
/>
<Button
onPress={() => {
this.textInput.focus();
}}
title="Press Me To show Keyboard"
color="#841584"
/>
<View
style={{
borderColor: "red",
borderWidth: 1,
padding: 16,
marginTop: 20
}}
>
<Text style={{ marginBottom: 8 }}>Show Typing Values:</Text>
<Text>{this.state.text}</Text>
</View>
</View>
);
}
}
App Preview
Related
I have encountered a weird issue in the newest react native where the value in the text input in a component remains when a tab is switched.
I can't figure what is going on and I thought it should re-render when tab is changed but it doesn't.
Here's my code
app.js
export default function App() {
const [tab, setTab] = useState("TAB1")
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity
style={{ borderRadius: 5, borderWidth: 1, marginRight: 5, padding: 20 }}
onPress={() => setTab("TAB1")}
>
<Text>Tab 1</Text>
</TouchableOpacity>
<TouchableOpacity
style={{ borderRadius: 5, borderWidth: 1, padding: 20}}
onPress={() => setTab("TAB2")}
>
<Text>Tab 2</Text>
</TouchableOpacity>
</View>
<View style={{ marginTop: 20}}>
{
tab === "TAB1" ? (
<View>
<InputComponent tab={tab} />
</View>
) : (
<View>
<InputComponent tab={tab} />
</View>
)
}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
marginTop: 100,
padding: 10
},
});
inputcomponent.js
export function InputComponent({ tab }) {
const [value, setValue] = useState("");
return (
<View>
<Text>{tab}</Text>
<TextInput placeholder="INPUT HERE" value={value} onChangeText={setValue}/>
</View>
)
}
It seems like the input component re-renders but the text input within it doesn't change.
Demo Issue
This is such a good question. This is because we are importing it once and passing it to two different components. It changes the tab but uses the same textinput state because they are using the same key.
To fix this pass in the key prop so React knows that tab changed:
{
tab === "TAB1" ? (
<View>
<InputComponent key={1} tab={tab} />
</View>
) : (
<View>
<InputComponent key={2} tab={tab} />
</View>
)
}
Snack: https://snack.expo.io/mVVLb9uId
Read about keys: https://reactjs.org/docs/lists-and-keys.html#keys
I'm a newbie in React Native.
What I'm trying to do is making a Google Maps-like app. At the MainMap.js screen, when we enter,the screen will immediately generate 2 search bars. The first one will have the text "Your location". The second one and so on will be empty for users to type in for searching location.
But, I'm having some problems with the FlatList component. In my PlaceInput component, I use the defaultValue, as a prop, for the text input. And then in the MainMap.js, I will have a state which initially be set as "Your Location", then I'll change it to null when the FlatList starts rendering from the 2nd PlaceInput component.
Here's the MainMap.js*
import React from 'react';
import {
TouchableWithoutFeedback,
StyleSheet,
Keyboard,
PermissionsAndroid,
Platform,
View,
Button,
FlatList,
Dimensions
} from 'react-native';
import PlaceInput from '../components/PlaceInput';
import axios from 'axios';
import PolyLine from '#mapbox/polyline';
import MapView, {Polyline, Marker} from 'react-native-maps';
import Geolocation from 'react-native-geolocation-service';
const INCREMENT = 1;
const HEIGHT = Dimensions.get('window').height;
const WIDTH = Dimensions.get('window').width;
class MainMap extends React.Component{
constructor(props){
super(props);
this.state={
_userLocationDisplayed: null,
userLatitude: 0,
userLongitude: 0,
numOfInput:[0,1],
counter: 1,
};
};
componentDidMount(){
this._requestUserLocation();
};
// Get user current location
// Ask user permission for current location
// Request the Directions API from Google
// Get the formatted_address & name from Google Places API
// Adding a search bar
onAddSearch(){
this.setState((state) => ({
counter: state.counter + INCREMENT,
numOfInput: [...state.numOfInput, state.counter],
}));
};
onChangeSearchDisplay(){
this.setState({
_userLocationDisplayed: null
})
};
render(){
return(
<TouchableWithoutFeedback onPress={this.hideKeyboard} >
<View style={styles.container} >
<View style={{height: HEIGHT/2.5 }}>
<FlatList
data={this.state.numOfInput}
keyExtractor={(item, index) => item}
renderItem={itemData => {
return(
<PlaceInput
id={itemData.item}
onDelete={this.onDeleteSearch}
showDirectionOnMap={this.showDirectionOnMap}
userLatitude={userLatitude}
userLongitude={userLongitude}
userLocationDisplayed={this.state._userLocationDisplayed}
/>
)
}}
/>
</View>
</View>
</TouchableWithoutFeedback>
)
}
}
//}
export default MainMap;
const styles = StyleSheet.create({
container:{
flex: 1
},
map:{
...StyleSheet.absoluteFillObject
},
});
Here's the PlaceInput component
import React from 'react';
import {
View,
TextInput,
StyleSheet,
Text,
Dimensions,
TouchableOpacity,
Keyboard,
} from 'react-native';
import axios from 'axios';
import _ from 'lodash'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
const WIDTH = Dimensions.get('window').width;
const HEIGHT = Dimensions.get('window').height;
class PlaceInput extends React.Component{
constructor(props){
super(props);
this.state={
...
}
...
}
render() {
// console.log(this.state);
// Code for displaying the suggestions from the Google Place API
// Don't care about it too much :)))
const predictions = this.state.predictions.map(prediction => {
const { id, structured_formatting, place_id } = prediction;
return(
<TouchableOpacity
key={id}
onPress={() => this.setDestination(structured_formatting.main_text, place_id)}
>
<View style={styles.suggestion}>
<Text style={styles.mainText}>{structured_formatting.main_text}</Text>
<Text style={styles.secText}>{structured_formatting.secondary_text}</Text>
</View>
</TouchableOpacity>
);
} )
return (
<View style={{flex: 1, flexDirection: 'column'}} key={this.props.id}>
<View style={styles.buttonContainer}>
<View style={{flex: 1, alignItems: 'center'}}>
<Text style={{fontSize: 8}}>{'\u25A0'}</Text>
</View>
<View style={{flex: 4}}>
<TextInput
key={this.id}
autoCorrect={false}
autoCapitalize='none'
style={styles.inputStyle}
placeholder='Search your places'
onChangeText={(input) => {
this.setState({destinationInput: input});
this.getPlacesDebounced(input);
}}
value={this.state.destinationInput}
{/*What I'm trying here as mentioned*/}
defaultValue={this.props.userLocationDisplayed}
/>
</View>
<View style={styles.rightCol}>
<TouchableOpacity onPress={() => this.props.onDelete(this.props.id)}>
<Icon name='delete' size={25} style={{alignSelf: 'center'}} />
</TouchableOpacity>
</View>
</View>
{predictions}
</View>
)
}
}
const styles = StyleSheet.create({
buttonContainer:{
flexDirection: 'row',
height: (HEIGHT - 690),
width: (WIDTH-48),
marginTop: 55,
padding: 5,
backgroundColor: 'white',
shadowColor: '#000000',
elevation: 7,
shadowRadius: 5,
shadowOpacity: 1,
borderRadius: 5,
alignItems: 'center',
alignSelf:'center'
},
inputStyle:{
fontFamily: 'sans-serif-thin',
fontSize: 16,
color: 'black',
fontWeight: 'bold'
},
suggestion:{
backgroundColor: 'white',
padding: 10,
borderWidth: 0.5,
width: (WIDTH-48),
alignSelf: 'center'
},
secText:{
color: '#777'
},
mainText:{
color: '#000'
},
rightCol:{
flex: 1,
borderLeftWidth: 1,
borderColor: '#ededed',
},
})
export default PlaceInput;
I'd love to hear your comments for helping me.
Also, feel free to give out other ways too since I think my way isn't optimized enough. And I'm building this for production too.
If I understand your question correctly, you're asking how to conditionally set a prop value based upon where it is in the flatlist data. Basically you want the first PlaceInput component to have a displayed "entered" text value of "Your Location" and the rest to have nothing.
Update API of PlaceInput to take in another prop to indicate displaying a default value or not.
PlaceInput.js
...
<TextInput
key={this.id}
autoCorrect={false}
autoCapitalize='none'
style={styles.inputStyle}
placeholder='Search your places'
onChangeText={(input) => {
this.setState({destinationInput: input});
this.getPlacesDebounced(input);
}}
value={this.state.destinationInput}
defaultValue={this.props.displayDefaultValue ? this.props.defaultValue : null}
/>
...
And pass in whether or not any specific PlaceInput should display it or not. Since you want only the first to display and the rest to not, using the array index is a good place to start. Here we can leverage the fact that in javascript 0 is a falsey value, while all other numbers are truthy. Using !index then !0 is true while !1, !2, etc, are all false.
MainMap.js
<FlatList
data={this.state.numOfInput}
keyExtractor={(item, index) => item}
renderItem={({ index, item }) => {
return(
<PlaceInput
id={item}
defaultValue="Your Location"
displayDefaultValue={!index} // index 0 is falsey, all others truthy
onDelete={this.onDeleteSearch}
showDirectionOnMap={this.showDirectionOnMap}
userLatitude={userLatitude}
userLongitude={userLongitude}
userLocationDisplayed={this.state._userLocationDisplayed}
/>
)
}}
/>
I take advantage of Drew Reese's answer but It doesn't work
I found out why it doesn't work because of the value prop, whose value is set by this.state.destinationInput which is " " in the state in the constructor. I again use Drew's way in the value prop instead, and it works
<TextInput
key={this.id}
autoCorrect={false}
autoCapitalize='none'
style={styles.inputStyle}
placeholder='Search your places'
onChangeText={(input) => {
this.setState({destinationInput: input});
this.getPlacesDebounced(input);
}}
value={this.props.displayDefaultValue ? this.props.defaultValue : this.state.destinationInput}
/>
BIG thanks to Drew Reese
currently i am using native base and having this type of text input for search bar
<Text>
Card Name
</Text>
<Header searchBar rounded style={{ backgroundColor: '#E9E9EF'}}>
<Item style={{ backgroundColor: 'lightgray', borderRadius: 5 }}>
<Icon name="ios-search" />
<Input placeholder="Search" onChangeText={(searchText) => this.setState({searchText})} value={this.state.searchText} />
</Item>
</Header>
I wanted to enable paste from clipboard, where user can copy some text from other places and paste it on this search input box. How can i do that?
You can use the clipboard API : https://facebook.github.io/react-native/docs/clipboard
or Textinput property : selectTextOnFocus
<TextInput selectTextOnFocus={true} />
If copy/paste not working for TextInput in react native. you can follow this code.
import React, { Component } from 'react';
import { TextInput, View } from 'react-native';
export class App extends Component {
constructor(props) {
super(props);
this.state = { text: '', testWidth: '99%' };
}
componentDidMount() {
setTimeout(() => {
this.setState({ textboxWidth: '100%' })
}, 100)
}
render() {
return (
<View style={{ marginTop: 40 }}>
<TextInput
style={{ width: this.state.textboxWidth }}
placeholder="Please type"
onChangeText={(text) => this.setState({ text })}
value={this.state.text}
/>
</View>
);
}
}
The solution found in this Git reply worked much better for me: https://github.com/facebook/react-native/issues/18926#issuecomment-490541013
<ScrollView
contentContainerStyle={Styles.contentContainerStyle}
keyboardShouldPersistTaps="handled"
removeClippedSubviews={false}>
<KeyboardAvoidingView>
<Text style={Styles.labelPageTitle}>
{'bla bla bla'}
</Text>
<Text>
{'bla bla bla'}
</Text>
<TextInput
onChangeText={text => this.setState({ title: text })}
style={Styles.textInput}
value={title}
/>
</KeyboardAvoidingView>
user RN gesture handler's TextInput instead of react native's
import {TextInput} from 'react-native-gesture-handler';
do add the following
selectTextOnFocus={true}
to the TexInput
You can use this community package which is suggested by the official docs
#react-native-clipboard/clipboard
import Clipboard from '#react-native-clipboard/clipboard'
You can easily access clipboard text by invoking an async function
const text = await Clipboard.getString()
This is parameter in TextInput secureTextEntry={true} works
I'm having trouble learning how to pass data between parent and child in React Native.
In my parent component I have a state property (audioPlaying) which is a Boolean value.
state = {
//The title informs the Button and TitleArea components
title: 'hello',
audioPlaying: false,
};
I'd like to change that value on the press of a button (onPress).
<Button
title={this.state.title}
onPress={this.playPauseHandler}
audioPlaying={this.state.audioPlaying}
/>
...by calling the playPauseHandler.
playPauseHandler = () => {
this.setState(prevState => ({
audioPlaying: !prevState.audioPlaying
}));
}
Then in my child (Button) Component I want to evaluate the audioPlaying state property. If it's true, I want to show one things and false I want to show something else.
<View style={styles.playBtnStyle}>
{this.props.audioPlaying === false ? (
<MaterialIcons
name='play-arrow'
size={50}
color="#87888C"
/>
) : (
<MaterialIcons
name='pause'
size={50}
color="#87888C"
/>
)}
}
</View>
However, when I run this I get undefined for the value of audioPlaying.
React Native Error Message
Here are the full files for both:
App.js
import React, { Component } from 'react';
import { View, StatusBar } from 'react-native';
import Carousel from './src/components/Carousel/Carousel';
import Button from './src/components/Button/Button';
import TitleArea from './src/components/TitleArea/TitleArea';
import MapArea from './src/components/MapArea/MapArea';
const styles = {
container: {
flex: 1,
justifyContent: 'space-between',
},
playArea: {
flex: 1,
},
};
export default class App extends Component {
state = {
//The title informs the Button and TitleArea components
title: 'hello',
audioPlaying: false,
};
playPauseHandler = () => {
this.setState(prevState => ({
audioPlaying: !prevState.audioPlaying
}));
}
render() {
return (
<View style={styles.container}>
<TitleArea title={this.state.title} />
<StatusBar hidden={false} />
<Carousel />
<MapArea />
<Button
title={this.state.title}
onPress={this.playPauseHandler}
audioPlaying={this.state.audioPlaying}
/>
</View>
);
}
}
Button.js
import React, { Component } from 'react';
import { Text, View, TouchableOpacity, Dimensions } from 'react-native';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
const { width } = Dimensions.get('window');
const height = width * 0.2;
const styles = {
textStyle: {
color: '#87888C',
fontSize: 18,
fontWeight: '600',
backgroundColor: 'white',
alignSelf: 'center',
},
buttonContainer: {
height,
flexDirection: 'row',
backgroundColor: 'white',
alignItems: 'center',
},
playBtnStyle: {
marginLeft: 50,
backgroundColor: 'white',
},
childStyle: {
flex: 1,
},
};
const button = (props) => {
return (
<View style={styles.buttonContainer}>
<TouchableOpacity>
<View style={styles.playBtnStyle}>
{this.props.audioPlaying === false ? (
<MaterialIcons
name='play-arrow'
size={50}
color="#87888C"
/>
) : (
<MaterialIcons
name='pause'
size={50}
color="#87888C"
/>
)}
}
</View>
</TouchableOpacity>
<View style={styles.childStyle}>
<Text style={styles.textStyle}>Chapter 1: {props.title}</Text>
</View>
</View>
);
}
export default button;
There is no this in the context of button. That is just a function returning JSX.
Instead, use props
<View style={styles.playBtnStyle}>
{props.audioPlaying === false ? (
<MaterialIcons
name='play-arrow'
size={50}
color="#87888C"
/>
) : (
<MaterialIcons
name='pause'
size={50}
color="#87888C"
/>
)}
</View>
Ok so I solved my own problem! (step one to being a developer)
Two issues:
Capturing Touch Events
React Native has what's called Touchables. According to the documentation these are "wrappers that make views respond properly to touches".
TouchableOpacity, the one I'm using:
On press down, the opacity of the wrapped view is decreased, dimming it. Opacity is controlled by wrapping the children in an Animated.View, which is added to the view hierarchy.
https://facebook.github.io/react-native/docs/touchablewithoutfeedback#onpress
All Touchables accept the onPress prop. So by adding the onPress prop to the Touchable, I'm able to capture the touch event instead of just firing it.
Passing Callback to Parent
This article helped me understand more about how a parent function can be called from a child.
https://medium.com/#thejasonfile/callback-functions-in-react-e822ebede766
So I'm calling playPause() (I renamed the prop and destructured it) in TouchableOpacity, which fires from a touch event causing state to change and component to re-render.
const button = (props) => {
const {
title,
audioPlaying,
playPause,
} = props;
return (
<View style={styles.buttonContainer}>
<TouchableOpacity onPress={() => playPause()}>
<View style={styles.playBtnStyle}>
{audioPlaying === false ? (
<MaterialIcons
name='play-arrow'
size={50}
color="#87888C"
/>
) : (
<MaterialIcons
name='pause'
size={50}
color="#87888C"
/>
)
}
</View>
</TouchableOpacity>
<View style={styles.childStyle}>
<Text style={styles.textStyle}>
Chapter 1:
{title}
</Text>
</View>
</View>
);
};
I have a login screen created using react-native.
How can I shift my screen up when the user is typing in the textInput?
Do I listen to the onFocus() event and use css styling to change the style of the view?
In 2017 (RN 0.43) there is special component for this: KeyboardAvoidingView
You can use ScrollView to control screen up and down movements. As long as user hasn't focused any TextInput, you can disable scroll. On focus, just shift up the scrollview using Content Offset prop.
<TextInput
onFocus={this.textInputFocused.bind(this)}
/>
textInputFocused() {
//do your stuff here. scroll screen up
}
Hope it helps!
import {KeyboardAvoidingView} from 'react-native';
<KeyboardAvoidingView style={styles.container} behavior="padding" enabled>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
/>
<Text style={{height: 100, marginTop: 20}}>1 test text after input</Text>
<Text style={{height: 100, marginTop: 20}}>2 test text after input</Text>
<Text style={{height: 100, marginTop: 20}}>3 test text after input</Text>
<Text style={{height: 100, marginTop: 20}}>4 test text after input</Text>
<Text style={{height: 100, marginTop: 20}}>5 test text after input</Text>
</KeyboardAvoidingView>
Run in snack :
https://snack.expo.io/H1BE5ZoXV
Night Fury's answer is pretty good, though wouldn't fuss with the ScrollView's contentOffset, I'd use the ScrollResponder:
render() {
return (
<ScrollView ref="myScrollView">
<TextInput
ref="myInput"
onFocus={this._scrollToInput.bind(this)}
/>
</ScrollView>
);
}
_scrollToInput {
const scrollResponder = this.refs.myScrollView.getScrollResponder();
const inputHandle = React.findNodeHandle(this.refs.myInput)
scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
inputHandle, // The TextInput node handle
0, // The scroll view's bottom "contentInset" (default 0)
true // Prevent negative scrolling
);
}
See the method definition: scrollResponderScrollNativeHandleToKeyboard
This package does a greate job, introduces a KeyboardAwareScrollView component that scrolls the view up matching the input with the keyboard, then scrolling back down.
And another solution, working with RN 0.2, this time instead of squashing the content it scrolls.
inputFocused: function(ref) {
this._scroll(ref, 75);
},
inputBlurred: function(ref) {
this._scroll(ref, 0);
},
_scroll: function(ref, offset) {
setTimeout(() => {
var scrollResponder = this.refs.myScrollView.getScrollResponder();
scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
React.findNodeHandle(this.refs[ref]),
offset,
true
);
});
},
...
render: function() {
return <View style={{flex: 1}}>
<ScrollView ref="myScrollView" keyboardDismissMode='interactive' contentContainerStyle={{flex: 1}}>
<TextInput
ref="myInput"
onFocus={this.inputFocused.bind(this, 'myInput')}
onBlur={this.inputBlurred.bind(this, 'myInput')} />
</ScrollView>
</View>
}
It's a crap shoot to get the native keyboard awareness functionality of ScrollView working. For my Android app, it works perfectly in one screen that is nearly identical as the other for which doesn't work. And on iOS, it just doesn't work. This is what's working for me:
import { Keyboard, ScrollView, StyleSheet, View } from 'react-native';
this.state = {
filler: false,
}
componentWillMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow.bind(this));
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide.bind(this));
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
_keyboardDidShow() {
this.setState({filler: true})
setTimeout(() => this.vertical && this.vertical.scrollToEnd({animated: true}), 0);
}
_keyboardDidHide() {
this.setState({filler: false})
}
...
return (
<ScrollView ref={ref => this.vertical = ref}>
<TextInput/>
{ this.state.filler ? <View style={styles.filler}/> : null }
</ScrollView>
)
styles.filler = {
height: 'Keyboard Height'
}
Note: This might only work if your <TextInput/> is at the bottom of the screen which it was in my case.