I'm trying to create a component similar to TouchableHighlight, but instead of highlighting the background I want to highlight text. I need to hook into the onPress event that fires at the point the user actually presses.
I've tried using TouchableWithoutFeedback and onPressIn, but it doesn't seem to fire. Here is the code I've been testing with.
class TouchableTextHighlight extends React.Component {
constructor (props) {
super(props)
this.state = {
isHighlighted: false
}
}
highlight () {
console.log('pressed')
this.setState({
isHighlighted: true
})
}
render () {
let textColor = this.state.isHighlighted ? '#F44336' : null
return (
<TouchableWithoutFeedback {...this.props} onPressIn={() => {this.highlight}} onPress={this.props.onPress}>
<View {...this.props}>
<Text
style={[this.props.textStyle, {color: textColor}]}
>{this.props.text}</Text>
</View>
</TouchableWithoutFeedback>
)
}
}
You need to invoke the method. Change:
onPressIn={() => {this.highlight}}
to
onPressIn={() => {this.highlight()}}
Your code is perfectly fine but you missed one thing which is very important to understand- Binding functions. Yourhighlight function not bound to the context. There are many ways to bind the function but here I will tell about one way which is efficient.
highlight () {
console.log('pressed')
this.setState({
isHighlighted: true
})
}
rewrite above function like this
highlight = () => {
console.log('pressed')
this.setState({
isHighlighted: true
})
}
If you want to read more about binding in react check out here
Related
When I am using custom tab bar through tabBar function tabBarHideOnKeyboard does not work but without tabBar function it works fine, any ideas on how I can make it work using tabBar function as well.
Add "softwareKeyboardLayoutMode": "pan" in app.json file under "android" key and then restart your expo server with expo start -c
<Tab.Navigator
tabBarOptions={{
showLabel: false,
keyboardHidesTabBar: true, // use this props to hide bottom tabs when keyboard shown
}}
the docs says to use tabBarHideOnKeyboard, but not working at all.
then i found keyboardHidesTabBar and works like a charm
I was using my customTab as well. And after huge amount of search, solved the problem with the help of Keyboard event listeners.
This is the best solution, I've found so far.
Here's my solution:
import { useEffect, useState } from "react";
import { Keyboard, Text, TouchableOpacity, View } from "react-native"
export default function TabBar({ state, descriptors, navigation }) {
// As default it should be visible
const [visible, setVisible] = useState(true);
useEffect(() => {
const showSubscription = Keyboard.addListener("keyboardDidShow", () => {
//Whenever keyboard did show make it don't visible
setVisible(false);
});
const hideSubscription = Keyboard.addListener("keyboardDidHide", () => {
setVisible(true);
});
return () => {
showSubscription.remove();
hideSubscription.remove();
};
}, []);
//Return your whole container like so
return visible && (
<View>
...
</View>
)
tabBarHideOnKeyboard or keyboardHidesTabBar options didn't work for me.
You'll get the tabBarHideOnKeyboard from the props for the custom tabBar.
tabBar={(props) => {
return (
<View>
{props.state.routes.map((route, index) => {
// You can replace Pressable and View with anything you like
return (
props.descriptors[route.key].options.tabBarHideOnKeyboard && (
<Pressable>
<View
style={{
width: 200,
height: 200,
backgroundColor: "green",
}}
/>
</Pressable>
)
);
})}
</View>
);
You can read more here
I have two screens, one list (Flatlist) and one filter screen where I want to be able to set some filters for the list. the list screen has the states "data" and "usedFilters". When I am switching to the filters screen, the states are set as navigation parameters for react navigation and then passed via navigation.navigate, together with the onChange function, as props to the filter screen. There they are read, and the filters screen class' state is set (usually with passed filters from the list screen, if no valid filters has been passed, some are initialized).
After that the filters can be changed. If that happens, the state of the filter screen gets updated.
If then the apply button is clicked the filter screens' state is passed to the onChange function and via that back to the list screen, the onChange function updates the state "usedFilters" state of the list screen. If the cancel button is pressed null is passed to the onChange function and there is no setState call.
Setting new states for the list screen works perfectly fine. the problem is, that when i press the cancel button (or the back button automatically rendered by react navigation) the changes are kept nevertheless. That only happens if the state has been changed before. So if there has never been applied a change and hence the "usedFitlers" state of the list screen is null, this behavior does not occur. Only if I already made some changes and hence the "usedFitlers" state of the list screen has a valid value which is passed to the filters screen the cancel or go back buttons won't work as expected.
I am using expo-cli 3 and tried on my android smartphone as well as the iOS simulator. Same behavior. I looked into it with chrome dev tools as well but i simply couldn't figure out where the "usedFitlers" state was updated.
I am using react native 0.60 and react navigation 3.11.0
My best guess is that for some reason the two states share the same memory or one is pointer to the other or sth like that. (Had problems like that with python some time ago, not knowing the it uses pointers when assigning variables).
Anyone got an idea?
List Screen:
export default class ListScreen extends React.Component {
state = { data: [], usedFilters: null };
static navigationOptions = ({ navigation }) => {
let data = navigation.getParam('data')
let changefilter = navigation.getParam('changeFilter')
let currfilter = navigation.getParam('currFilter')
return {
headerTitle:
<Text style={Styles.headerTitle}>{strings('List')}</Text>,
headerRight: (
<TouchableOpacity
onPress={() => navigation.navigate('FilterScreen', {
dataset: data, onChange: changefilter, activeFilters:
currfilter })} >
<View paddingRight={16}>
<Icon name="settings" size={24} color=
{Colors.headerTintColor} />
</View>
</TouchableOpacity>
),
};
};
_onChangeFilter = (newFilter) => {
if (newFilter) {
this.setState({ usedFilters: newFilter })
this.props.navigation.setParams({ currFilter: newFilter });
} // added for debugging reasons
else {
this.forceUpdate();
let a = this.state.usedFilters;
}
}
_fetchData() {
this.setState({ data: fakedata.results },
() => this.props.navigation.setParams({ data: fakedata.results,
changeFilter: this._onChangeFilter }));
}
componentDidMount() {
this._fetchData();
}
render() {
return (
<ScrollView>
<FlatList/>
// Just data rendering, no problems here
</ScrollView>
);
}
}
Filter Screen:
export default class FilterScreen extends React.Component {
static navigationOptions = () => {
return {
headerTitle: <Text style={Styles.headerTitle}> {strings('filter')}
</Text>
};
};
state = { currentFilters: null }
_onChange = (filter, idx) => {
let tmp = this.state.currentFilters;
tmp[idx] = filter;
this.setState({ currentFilters: tmp })
}
_initFilterElems() {
const filters = this.props.navigation.getParam('activeFilters');
const dataset = this.props.navigation.getParam('dataset');
let filterA = [];
let filterB = [];
let filterC = [];
if (filters) {
// so some checks
} else {
// init filters
}
const filterElements = [filterA, filterB, filterC];
this.setState({ currentFilters: filterElements })
}
componentDidMount() {
this._initFilterElems()
}
render() {
const onChange = this.props.navigation.getParam('onChange');
return (
<ScrollView style={Styles.screenView}>
<FlatList
data={this.state.currentFilters} // Listeneinträge
keyExtractor={(item, index) => 'key' + index}
renderItem={({ item, index }) => (
<FilterCategory filter={item} name={filterNames[index]}
idx={index} onChange={this._onChange} />
)}
ItemSeparatorComponent={() => <View style=
{Styles.listSeperator} />}
/>
<View style={Layout.twoHorizontalButtons}>
<TouchableOpacity onPress={() => {
onChange(this.state.currentFilters);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('apply')} </Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => {
onChange(null);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('cancel')}
</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView >
);
}
}
So when I press the cancel button, null is returned to the _onChangeFilter function of the list screen. This part works, and according to console.log and the debugger, the setState is not called. But if i set a breakpoint within the else part, i can see that this.state.usedFilters has changed.
Ok after a while i figured it out. The problem was that the whole filters list was always just referenced since react native (js) seems to always use references, even when changing sub-parts of the lists.
fixed that by using lodash cloneDeep.
The question is almost similar to this one :
touchableopacity onpress function undefined (is not a function) React Native
But the problem is, I am getting the error despite the fact that I have bind the function. Here is my TouchableOpacity component:
<TouchableOpacity style={styles.eachChannelViewStyle} onPress={() => this.setModalVisible(true)}>
{item.item.thumbnail ?
<Image style={styles.everyVideoChannelThumbnailStyle} source={{uri: item.item.thumbnail}} />
: <ActivityIndicator style= {styles.loadingButton} size="large" color="#0000ff" />}
<Text numberOfLines={2} style={styles.everyVideoChannelVideoNameStyle}>
{item.item.title}
</Text>
</TouchableOpacity>
And this is my setModalVisible function:
setModalVisible(visible) {
console.error(" I am in set modal section ")
this.setState({youtubeModalVisible: visible});
}
Also, I have bind the function in constructor as follows:
this.setModalVisible = this.setModalVisible.bind(this);
But, I am still getting same error that undefined is not a function. Any help regarding this error?
The render method and your custom method must be under the same scope. In code below I have demonstrated the same. I hope you will modify your code accordingly as I assume you got the gist :)
class Demo extends Component {
onButtonPress() {
console.log("click");
}
render() {
return (
<View>
<TouchableOpacity onPress={this.onButtonPress.bind(this)}>
<Text> Click Me </Text>
</TouchableOpacity >
<View>
);
}
}
Alternatively binding method in constructor will also work
class Demo extends Component {
constructor(props){
super(props);
this.onButtonPress= this.onButtonPress.bind(this);
}
onButtonPress() {
console.log("click");
}
render() {
return (
<View>
<TouchableOpacity onPress={this.onButtonPress()}>
<Text> Click Me </Text>
</TouchableOpacity >
<View>
);
}
}
I'm not sure if this will help but I write my functions this way and haven't encountered this problem.
If I were you I'd try binding the function in the place where you declare it.
setModalVisible = (visible) => {
this.setState({ youtubeModalVisible: visible });
}
If you do this, you don't have to bind in the constructor.
constructor(props) {
...
// Comment this out to see it will still bind.
// this.setModalVisible = this.setModalVisible.bind(this);
...
}
Lastly, if this function will only set the modal's state to visible, you might want to remove the argument and pass it this way.
<TouchableOpacity style={styles.eachChannelViewStyle} onPress={this.setModalVisible}>
...
</TouchableOpacity>
// Refactored function declaration would look like this
setModalVisible = () => {
this.setState({ youtubeModalVisible: true });
}
I have a FlatList and I am trying to but a button below the items and when you click on the button it should display the name of the item in an alert.
class TopMovies extends React.Component {
constructor(props) {
super(props);
this.state = {
apiTopLoaded: false,
topPopularMovies: [],
}
this.conditionalTopPopular = this.conditionalTopPopular.bind(this);
this.mybuttonclick = this.mybuttonclick.bind(this);
}
componentDidMount() {
fetch('someurls')
.then((response)=>{
response.json()
.then((popularMovies) => {
this.setState({
apiTopLoaded: true,
topPopularMovies: popularMovies.results,
})
})
})
mybuttonclick() {
Alert.alert(item.original_title)
}
conditionalTopPopular() {
if(this.state.apiTopLoaded===true) {
return(
<FlatList
horizontal={true}
data={this.state.topPopularMovies}
renderItem={({ item }) => (
<View>
<Text>{item.original_title}</Text>
<Button onPress={this.mybuttonclick} title="hello"/>
</View>
)}
keyExtractor={item => item.id}
/>
</View>
)
}
}
I can see all the movie names in the list and I see buttons below the movie names, but when I click on it, it says "cant find variable item". The function mybuttonclick should alert the item.original_title prop because it displays correctly in the flatlist. Please help.
Change your function like this:
mybuttonclick(movieTitle) {
Alert.alert(movieTitle)
}
And pass in the the movie title like this:
<Button onPress={this.mybuttonclick(item.original_title)} title="hello"/>
You should use the fat arrow function in the onPress like this.
<Button onPress={() => this.mybuttonclick(item.original_title)} title="hello"/>
I have this simple code of a TextInput that I want it to get focus when it first renders and on submits. However, it does not get focus at all.
render() {
return (
<TextInput
ref={(c) => this._input = c}
style={[styles.item, this.props.style]}
placeholder={"New Skill"}
onChangeText={(text) => {
this.setState({text})
}}
onSubmitEditing={(event) => {
this.props.onSubmitEditing(this.state.text);
this._input.clear();
this._input.focus();
}}
/>
);
}
componentDidMount() {
this._input.focus();
}
So my assumption is true. Try to focus is failed, this._input doesn't contain anything when componentDidMount called, because render function still not called yet and no reference for it.
So the solution for now is delay it a little bit until render function already called.
Thats why wrap the code inside setTimeout function quite helpful. Anyway i admit it, it is a little bit tricky. Would be great if someone find the better way.
componentDidMount() {
setTimeout(() => this._input.focus(), 250);
}
You can use autoFocus property of TextInput and set it to true. It will focus TextInput on componentDidMount automatically.I tested it and it's focusing input on both componentDidMount and onSubmitEditing.
render() {
return (
<TextInput
ref={(c) => this._input = c}
placeholder={"New Skill"}
autoFocus={true}
onChangeText={(text) => {
this.setState({text})
}}
onSubmitEditing={() => {
this.props.onSubmitEditing(this.state.text);
this._input.clear();
this._input.focus();
}}
/>
);
}