in my Tinder like react-native app I have a huge list of tags that people can add to their profile when they click on a tag, I want the tags the user adds to be a different color however, the background color is not changing... only time everything looks as it should is when I change the view/code and the component refreshes, then the colored tags appear.
This is my code:
const [selectedItems, setSelectedItems] = useState([]);
const addItem = (id) =>
{
console.log(id);
if(selectedItems.includes(id))
{
let index = selectedItems.findIndex(interest => interest == id);
if(index > -1)
{
let selectedInterests = selectedItems;
selectedInterests.splice(index, 1)
setSelectedItems(selectedInterests);
}
}
else
{
if(selectedItems.length < 10)
{
let selectedInterests = selectedItems;
selectedInterests.push(id);
setSelectedItems(selectedInterests);
}
}
};
{root.userStore.tags.map((item,index) => {return (
<Text key={item.id} onPress={ ()=>{ addItem(item.id) } } style={{ fontSize:17,padding:6,paddingLeft:10,paddingRight:10, ...selectedItems.includes(item.id) ? { color:'white', borderColor:'#E13545',backgroundColor:'#E13545' } : { color:'rgb(100,100,100)',borderColor:'rgba(0,0,0,0.1)',backgroundColor:'white' },borderRadius:35,borderWidth:1,margin:5 }}>{I18n.t(item.title)}</Text>
)
})}
Thanks in advance
Something does not appear to be correct with your destructuring, to use multiple styles you can try using a styles array, I did not get a chance to run this but should work. You can check documentation of the style array here, you may also consider using a flatlist for this if possible
{
root.userStore.tags.map((item, index) => {
return (
<Text
key={item.id}
onPress={() => {
addItem(item.id);
}}
style={[
{
fontSize: 17,
padding: 6,
paddingLeft: 10,
paddingRight: 10,
borderRadius: 35,
borderWidth: 1,
margin: 5,
},
selectedItems.includes(item.id)
? {
color: "white",
borderColor: "#E13545",
backgroundColor: "#E13545",
}
: {
color: "rgb(100,100,100)",
borderColor: "rgba(0,0,0,0.1)",
backgroundColor: "white",
},
]}
>
{I18n.t(item.title)}
</Text>
);
});
Related
Hello I've been looking through several threads on stackoverflow but I haven't been able to solve my problem. I have an app where you can save movies to a watchlist. On this specific screen I want to display a users watchlist and give them the ability to delete it from the list. Currently the function is indeed deleting the movie from the list and removing it from firebase but i can't get my screen to rerender to visually represent the deletion.
This is the code right now:
export default function MovieWatchlistTab(props: any) {
let { movies } = props;
let movieWatchlist: any[] = [];
const [watchlistSnapshot, setWatchlistSnapshot] = useState();
const user: firebase.User = auth().currentUser;
const { email } = user;
const watchlistRef = firestore().collection("Watchlist");
useEffect(() => {
getWatchlistSnapshot();
}, []);
const getWatchlistSnapshot = async () => {
setWatchlistSnapshot(await watchlistRef.where("userId", "==", email).get());
};
const convertDataToArray = () => {
const convertedMovieList = [];
for (let movie in movies) {
let newMovie = {
backdrop: movies[movie].backdrop,
overview: movies[movie].overview,
release: movies[movie].release,
title: movies[movie].title,
};
convertedMovieList.push(newMovie);
}
movieWatchlist = convertedMovieList;
};
const renderMovieList = () => {
convertDataToArray();
return movieWatchlist.map((m) => {
const handleOnPressDelete = () => {
const documentRef = watchlistRef.doc(watchlistSnapshot.docs[0].id);
const FieldValue = firestore.FieldValue;
documentRef.set(
{
movies: {
[m.title]: FieldValue.delete(),
},
},
{ merge: true }
);
movieWatchlist.splice(
movieWatchlist.indexOf(m),
movieWatchlist.indexOf(m) + 1
);
console.log("movieWatchlist", movieWatchlist);
};
const swipeButtons = [
{
text: "Delete",
color: "white",
backgroundColor: "#b9042c",
onPress: handleOnPressDelete,
},
];
return (
<Swipeout right={swipeButtons} backgroundColor={"#18181b"}>
<View key={m.title} style={{ marginTop: 10, flexDirection: "row" }}>
<Image
style={{ height: 113, width: 150 }}
source={{
uri: m.backdrop,
}}
/>
<View>
<Text
style={{
flex: 1,
color: "white",
marginLeft: 10,
fontSize: 17,
}}
>
{m.title}
</Text>
<Text style={{ flex: 1, color: "white", marginLeft: 10 }}>
Release: {m.release}
</Text>
</View>
</View>
</Swipeout>
);
});
};
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#18181b",
}}
>
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{
width: Dimensions.get("window").width,
}}
>
{renderMovieList()}
</ScrollView>
</View>
);
}
I've been trying to play around with useStates and I think the answer is in that direction but I just can't seem to get it to work anyway. Any help would be appreciated!
There are a few lines in your code that show a misunderstand of React state. You have a value let movieWatchlist: any[] = []; that you reassign in convertDataToArray() and mutate in handleOnPressDelete. That's just not how we do things in React and it's not going to trigger updates properly. movieWatchlist either needs to be a stateful variable created with useState.
Do the movies passed in through props change? If they do, then you don't need to store them in state here. You could just return an array from convertDataToArray() rather than setting a variable and returning void.
To be honest it's really not clear what convertDataToArray is even doing as it seems like newMovie is either the same or a subset of the original movie object. If the point is to remove the other properties aside from these four, that's not actually needed. If the prop movies is already an array, just delete this whole function and use movies directly. If it's a keyed object, use Object.values(movies) to get it as an array.
I'm thoroughly confused as to what we are getting from props and what we are getting from firebase. It seems like we would want to update the snapshot state after deletion, but you only run your useEffect once on mount.
You may still have errors, but this code should be an improvement:
interface Movie {
backdrop: string;
overview: string;
release: string;
title: string;
}
const MovieThumbnail = (props: Movie) => (
<View key={props.title} style={{ marginTop: 10, flexDirection: "row" }}>
<Image
style={{ height: 113, width: 150 }}
source={{
uri: props.backdrop
}}
/>
<View>
<Text
style={{
flex: 1,
color: "white",
marginLeft: 10,
fontSize: 17
}}
>
{props.title}
</Text>
<Text style={{ flex: 1, color: "white", marginLeft: 10 }}>
Release: {props.release}
</Text>
</View>
</View>
);
export default function MovieWatchlistTab() {
const [watchlistSnapshot, setWatchlistSnapshot] = useState<DocumentSnapshot>();
const user: firebase.User = auth().currentUser;
const { email } = user;
const watchlistRef = firestore().collection("Watchlist");
const getWatchlistSnapshot = async () => {
const results = await watchlistRef.where("userId", "==", email).get();
setWatchlistSnapshot(results.docs[0]);
};
useEffect(() => {
getWatchlistSnapshot();
}, []);
const deleteMovie = async (title: string) => {
if ( ! watchlistSnapshot ) return;
const documentRef = watchlistRef.doc(watchlistSnapshot.id);
const FieldValue = firestore.FieldValue;
await documentRef.set(
{
movies: {
[title]: FieldValue.delete()
}
},
{ merge: true }
);
// reload watch list
getWatchlistSnapshot();
};
// is this right? I'm just guessing
const movies = ( watchlistSnapshot ? watchlistSnapshot.data().movies : []) as Movie[];
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#18181b"
}}
>
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{
width: Dimensions.get("window").width
}}
>
{movies.map((m) => (
<Swipeout
right={[
{
text: "Delete",
color: "white",
backgroundColor: "#b9042c",
// need to pass the title to the delete handler
onPress: () => deleteMovie(m.title)
}
]}
backgroundColor={"#18181b"}
>
<MovieThumbnail {...m} />
</Swipeout>
))}
</ScrollView>
</View>
);
}
I'm new to React Native and Javascript, but I cannot find anything online to help me with this problem that I am having.
My "macSong" functions if statements never pass and I'm unsure as to why, I feel like the logic behind the code is sound, yet my console is still outputting "Selected Item is Unknown" because all of the if else statements are "not true" when at least one of them should be true if the dropdown menu has been used before pressing the button. My macSong function is just above my Stylesheet, at the bottom of my code.
If anyone can help me that would be amazing, thank you in advance and be sure to let me know if you need any more information to help you answer my question!
import React, { Component, Fragment } from 'react';
import { StyleSheet, Text, TextInput, View, Button, Alert } from 'react-native';
import SearchableDropdown from 'react-native-searchable-dropdown';
var items =[
{
id: 1,
name: 'Happy Music'
},
{
id: 2,
name: 'Sad Music'
},
{
id: 3,
name: 'Chill Music'
},
{
id: 4,
name: 'Hype Music'
}
];
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
selectedItems: ''
}
}
render() {
return (
<View style={ styles.screen }>
<Fragment>
{/* Title */}
<View style={ styles.title }>
<Text> Which Mac Miller Song Matches Your Mood? </Text>
</View>
{/* Single Dropdown Menu */}
<SearchableDropdown
onItemSelect={(item) => {
const items = this.state.selectedItems;
this.setState({ selectedItems: [...items, item]});
}}
containerStyle={{ padding: 25, alignSelf: 'center' }}
onRemoveItem={(item, index) => {
const items = this.state.selectedItems.filter((sitem) => sitem.id !== item.id);
this.setState({ selectedItems: [...items, item] });
}}
itemStyle={{
padding: 10,
marginTop: 2,
backgroundColor: '#ddd',
borderColor: '#bbb',
borderWidth: 1,
borderRadius: 5,
}}
itemTextStyle={{ color: '#222' }}
itemsContainerStyle={{ maxHeight: 140 }}
items={items}
defaultIndex={''}
resetValue={false}
textInputProps={
{
placeholder: "What kind of music do you want to hear?",
underlineColorAndroid: "transparent",
style: {
padding: 12,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 5,
},
}
}
listProps={
{
nestedScrollEnabled: true,
}
}
/>
{/* Button */}
<View style={ styles.button }>
<Button
title="Press me for a Mac Miller song!"
onPress={() =>
this.macSong()}
/>
</View>
</Fragment>
</View>
);
}
/* Different Mood Function */
macSong() {
console.log(this.state.selectedItems)
if (this.state.selectedItems.some(item => item.name === 'Happy Music')) {
let songs = ['best day ever', 'kool aid & frozen pizza', 'nikes on my feet'];
let song = songs[Math.floor(Math.random() * songs.length)];
console.log(song);
} else if (this.state.selectedItems.some(item => item.name === 'Sad Music')) {
let songs = ['self care', 'ROS', 'stay', 'whats the use'];
let song = songs[Math.floor(Math.random() * songs.length)];
console.log(song);
} else if (this.state.selectedItems.some(item => item.name === 'Chill Music')) {
let songs = ['good news', 'claymation', 'the star room'];
let song = songs[Math.floor(Math.random() * songs.length)];
console.log(song);
} else if (this.state.selectedItems.some(item => item.name === 'Hype Music')) {
let songs = ['donald trump', 'remember', 'weekend'];
let song = songs[Math.floor(Math.random() * songs.length)];
console.log(song);
} else {
console.log("Selected Item is Unknown");
}
}
}
{/* StyleSheet */}
const styles = StyleSheet.create({
button: {
padding: 10,
alignSelf: 'center'
},
title: {
padding: 30,
alignSelf: 'center',
textAlign: 'center'
}
});
this.state.selectedItems is an array of objects of the following form:
{ id: Number, name: String }
If we look at your if statements, your conditionals look like:
if (this.state.selectedItems.includes('Happy Music')) {
Your array of objects will never include a plain string. You want to check if in includes an object with the name of that String. You can use some for that. Something like this:
if (this.state.selectedItems.some(item => item.name === 'Happy Music'))
Hopefully that gets you back to a working state. Peeking at your code it looks like you have opportunity to simplify this logic after this.
First, you are calling macSong() with no arguments on button press, but on its definition you have an argument macSong(selectedArguments) { ... }. This is not needed because you are grabbing this.state.selectedItems within your function. So the correct would be macSong() { ... }.
Then, it should be this.state.selectedItems.map(item => item.name).includes("Happy Music") etc., because item is an object that has name as a property, and when you check if an object includes("string") you need to check an array of strings (which map() will do).
I have this code where I am trying to render an element:
render() {
//console.log('this.state.showRespondTo:',this.state.showRespondTo);
return (
<View style={{flex:1}}>
{this.displayMessage()}
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<MultiSelectList
style={{backgroundColor: '#ffe4c4'}}
data={this.state.items}
renderItem={this.renderListItems}
numColumns={2}
contentContainerStyle={{}}
onEndReachedThreshold={0.5}
maxToRenderPerBatch={2}
initialNumToRender={4}
ListHeaderComponent={this.renderHeader}
getItemLayout={(data, index) => (
{length: Dimensions.get('window').height/2, offset: Dimensions.get('window').height/2 * index, index}
)}
backgroundColor={this.state.backgroundColor}
//contentContainerStyle={{backgroundColor: '#1e4683'}}
/>
</View>
</TouchableWithoutFeedback>
</View>
);
}
displayMessage() {
if(this.state.showRespondTo) {
console.log('this.state.showRespondTo:', this.state.showRespondTo);
return ( <RespondToInquiry/> )
}
}
This is what RespondToInquiry is:
import React, { Component } from 'react'
import {
Text,
TextInput,
TouchableOpacity,
TouchableHighlight,
TouchableWithoutFeedback,
View,
Dimensions,
Keyboard
} from 'react-native'
import { Calendar, CalendarList, Agenda } from 'react-native-calendars';
import { Map } from 'immutable';
import Modal from 'react-native-modal';
import firebase from 'react-native-firebase';
export default class RespondToInquiry extends React.PureComponent {
constructor() {
super()
this.state = {
_markedDates: {},
//_selectedDay: new Date().dateString,
modalVisible: false,
message: 'Hi, I would like to rent an item from you.',
rentButtonBackground: '#6de3dc',
datesArray: []
}
this.onDayPress = this.onDayPress.bind(this)
}
/*initialState = {
[new Date()]: { 'selected': false,
customStyles: {
container: {
backgroundColor: '#6de3dc',
},
text: {
color: 'white',
fontWeight: 'bold'
},
},
}
}*/
showCalendar = () => {
return (
<Calendar
style={{
borderWidth: 0,
borderRadius: 4,
}}
theme={{
todayTextColor: '#6de3dc',
selectedDayBackgroundColor: '#6de3dc',
}}
markingType={'custom'}
markedDates={this.state._markedDates}
// Initially visible month. Default = Date()
// Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
minDate={new Date()}
// Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined
// Handler which gets executed on day press. Default = undefined
onDayPress={day => this.onDayPress(day)}
// Handler which gets executed on day long press. Default = undefined
onDayLongPress={day => {
console.log('selected day', day)
}}
// Month format in calendar title. Formatting values: http://arshaw.com/xdate/#Formatting
monthFormat={'MMM d, yyyy'}
// Handler which gets executed when visible month changes in calendar. Default = undefined
onMonthChange={month => {
console.log('month changed', month)
}}
// Hide month navigation arrows. Default = false
//hideArrows={true}
// Replace default arrows with custom ones (direction can be 'left' or 'right')
//renderArrow={(direction) => (<Arrow />)}
// Do not show days of other months in month page. Default = false
hideExtraDays={true}
// If hideArrows=false and hideExtraDays=false do not switch month when tapping on greyed out
// day from another month that is visible in calendar page. Default = false
//disableMonthChange={true}
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
firstDay={0}
// Hide day names. Default = false
//hideDayNames={true}
// Show week numbers to the left. Default = false
//showWeekNumbers={true}
// Handler which gets executed when press arrow icon left. It receive a callback can go back month
onPressArrowLeft={substractMonth => substractMonth()}
// Handler which gets executed when press arrow icon left. It receive a callback can go next month
onPressArrowRight={addMonth => addMonth()}
/>
)
}
onDayPress = (day) => {
const _selectedDay = day.dateString;
let marked = true;
if (this.state._markedDates[_selectedDay]) {
// Already in marked dates, so reverse current marked state
marked = !this.state._markedDates[_selectedDay].selected;
console.log('marked:', marked);
// Create a new object using object property spread since it should be immutable
// Reading: https://davidwalsh.name/merge-objects
const updatedMarkedDates = {...this.state._markedDates, ...{ [_selectedDay]: { 'selected': marked,
customStyles: {
container: {
backgroundColor: '#6de3dc',
},
text: {
color: 'white',
fontWeight: 'bold'
},
},
} } }
// Triggers component to render again, picking up the new state
this.setState({ _markedDates: updatedMarkedDates }, () => {
console.log('updatedMarkedDates:', this.state._markedDates);
});
}
else {
// Create a new object using object property spread since it should be immutable
// Reading: https://davidwalsh.name/merge-objects
const updatedMarkedDates = {...this.state._markedDates, ...{ [_selectedDay]: { 'selected': true,
customStyles: {
container: {
backgroundColor: '#6de3dc',
},
text: {
color: 'white',
fontWeight: 'bold'
},
},
} } }
// Triggers component to render again, picking up the new state
this.setState({ _markedDates: updatedMarkedDates }, () => {
console.log('updatedMarkedDates:', this.state._markedDates);
});
}
}
waitToStoreDates = () => new Promise((resolve) => {
let x = 0;
let datesArray = [];
for(date in this.state._markedDates) {
console.log("Date Object: ",date);
if(this.state._markedDates[date].selected) {
datesArray.push(date);
}
x++;
}
if(x == Object.keys(this.state._markedDates).length) {
console.log("x:",x);
console.log('datesArray in waitToStoreDates:', datesArray);
this.state.datesArray = datesArray;
resolve();
}
})
async processMarkedDates() {
await this.waitToStoreDates();
}
setModalVisible(visible) {
this.setState({ modalVisible: visible })
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate in InitiateRent:', this.props, prevProps, prevState, snapshot)
}
_renderModalContent = () => (
<TouchableWithoutFeedback onPress={() => {console.log('tapped')}}>
<View
style={{
paddingTop: 5,
paddingBottom: 10,
paddingLeft: 10,
paddingRight: 10,
marginTop: 0,
flex: 1,
width: Dimensions.get('window').width
}}>
<View style={{ flexDirection: 'column', justifyContent: 'space-between', flex: 1 }}>
<View style={{ flexDirection: 'column', flex: 1 }}>
<Text
style={{
flex: 0,
width: Dimensions.get('window').width,
color: 'white',
fontWeight: '700',
marginBottom: 5,
}}>
Date(s) Needed:
</Text>
{this.showCalendar()}
</View>
<View style={{ flexDirection: 'column', flex: 0.2, marginBottom: 10 }}>
<TextInput
style={{
width: 280,
flex: 1,
borderColor: 'gray',
borderWidth: 1,
backgroundColor: '#ffffff',
paddingLeft: 5,
borderRadius: 4,
}}
onChangeText={text => this.setState({ message: text })}
value={this.state.message}
multiline={true}
numberOfLines={2}
/>
</View>
<View style={{ flex: 0.1, borderRadius: 4, borderWidth: 0 }}>
<TouchableOpacity
activeOpacity={1}
style={{
backgroundColor: this.state.rentButtonBackground,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
width: 280,
borderRadius: 4,
borderWidth: 0,
}}
onPress={() => {
this.setState({ rentButtonBackground: '#94ebe6' })
setTimeout(() => {
this.setState({ rentButtonBackground: '#6de3dc' })
let timestamp = new Date().getTime().toString();
this.processMarkedDates();
console.log("this.state.datesArray", this.state.datesArray);
dataChat = {
"title": "Rental Inquiry",
"lastMessage": this.state.message,
"timestamp": timestamp
}
dataMessage = {}
dataMessage[timestamp] = {
"name": "eamon",
"message": this.state.message,
"timestamp": timestamp,
"dates": JSON.stringify(this.state.datesArray)
};
this.sendRentMessage(dataChat, dataMessage, timestamp)
this.setModalVisible(false)
}, 1)
}}>
<Text
style={{
backgroundColor: this.state.rentButtonBackground,
textAlign: 'center',
color: 'white',
fontWeight: '900',
fontSize: 18,
borderRadius: 4,
borderWidth: 0,
}}>
SEND
</Text>
</TouchableOpacity>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
componentDidMount() {
firebase.messaging()
.hasPermission()
.then(enabled => {
if (!enabled) {
console.log('permissions disabled');
this._getPermission();
}
console.log('permissions enabled');
firebase.messaging().subscribeToTopic('all').catch((error) => {alert(error)});
firebase.messaging().getToken()
.then(fcmToken => {
if (fcmToken) {
//USE THIS FOR INDIVIDUAL DEVICE MESSAGES?
console.log(fcmToken);
} else {
alert("User doesn't have a token yet");
}
}).catch((error) => {
alert(error);
});
}).then(() => {
}).catch((error) => {alert(error)});
}
_getPermission = () => {
firebase.messaging()
.requestPermission()
.catch(error => {
// User has rejected permissions
// this._getPermission();
Alert.alert(
'ERROR',
"You must enable push notifications for the messaging system to work! If you don't you won't be able to use SnagIt! Please enable notificaitons in your phone - go to: Settings > Notifications > SnagIt.",
[
{text: 'OK', onPress: () => console.log('OK Pressed')},
],
{ cancelable: false }
)
});
}
sendRentMessage(dataChat, dataMessage, timestamp) {
// Add a new document with a generated id. //user-user //send generated ID and then change to message id in cloud
/*let addChat = firebase.firestore().collection('chats').doc(timestamp);
// Add a new document with a generated id. //user-user //send generated ID and then change to message id in cloud
let addMessage = firebase.firestore().collection('messages').doc(timestamp);
// Set the 'capital' field of the city
addChat.update(dataChat).then(() => {
// Set the 'capital' field of the city
addMessage.update(dataMessage).catch((error) => {
//alert(error);
addMessage.set(dataMessage).catch((error) => {
alert(error);
});
});
}).catch((error) => {
//alert(error);
addChat.set(dataChat).catch((error) => {
alert(error);
}).then(() => {
addMessage.update(dataMessage).catch((error) => {
//alert(error);
addMessage.set(dataMessage).catch((error) => {
alert(error);
});
});
})
});*/
}
render() {
return (
<View style={{flex: 1}}>
<Modal
animationType="slide"
transparent={true}
visible={this.state.modalVisible}
onBackdropPress ={() => {console.log("backdrop pressed"); this.setModalVisible(false)}}>
{this._renderModalContent()}
</Modal>
</View>
)
}
}
For console output I see:
this.state.showRespondTo: – true
So I know that the conditional is changing to the state where it should render the component. I also see half of the screen (where the component should be) as blank/white background. This is because of how I have my flexbox setup, and is the behavior I want. But the element that is supposed to be rendered isn't showing up. The state of the conditional changes on a didFocus listener, like this:
componentDidMount() {
this._sub = this.props.navigation.addListener(
'didFocus',
() => {
if(this.props.navigation.getParam('data', '') != '') {
console.log('showRespondTo fired.');
this.setState({showRespondTo: true});
}
}
);
....
}
Why doesn't my InitiateRent component render where the blank/white area is (see image below)?
Just something silly, this.state.modalVisible is set to false initially so the modal wasn't showing up.
I want some guidance how to make it possible to swipe between react native elements "Buttongroups".
I found a "swiper" library but cant make it work with the buttongroup.
https://github.com/leecade/react-native-swiper
Have anyone any idé how to implement this ?
I want to be able to both swipe and push one of the buttons.
My Component including ButtonGroup :
class Users extends Component {
constructor () {
super()
this.state = {
selectedIndex: 0,
loading: false
}
this.updateIndex = this.updateIndex.bind(this)
}
// Call fetchList with 0 to get access to all users
componentWillMount () {
let i = 0
this.props.fetchList(i)
this.props.fetchProfileData()
}
// updates the selectedIndex and calls the methods with the selectedindex
updateIndex (selectedIndex) {
this.setState({selectedIndex})
this.fetchAllUsers(selectedIndex)
this.fetchFemale(selectedIndex)
this.fetchMale(selectedIndex)
}
fetchAllUsers (index) {
if (index === 0) {
this.props.fetchList(index)
}
}
fetchFemale (index) {
if (index === 1) {
this.props.fetchList(index)
}
}
fetchMale (index) {
if (index === 2) {
this.props.fetchList(index)
}
}
renderItem ({ item }) {
return <ListUserItem user={item} />
}
render () {
const buttons = ['All', 'Female', 'Male']
const { selectedIndex } = this.state
return (
<ImageBackground
source={require('../../assets/image.jpg')}
style={styles.container}
>
<HeaderBlack />
<View>
<BlurView
style={styles.absolute}
blurType='dark'
blurAmount={0.001}
height={695} />
<View style={{ justifyContent: 'center', alignItems: 'center' }} >
<ButtonGroup
onPress={this.updateIndex.bind(this)}
selectedIndex={selectedIndex}
selectedButtonStyle={{backgroundColor: 'black'}}
buttons={buttons}
containerStyle={{ backgroundColor: 'transparent', height: 23, width: 200, marginTop: 30, marginBottom: -20, justifyContent: 'center', alignItems: 'baseline' }}
textStyle={{fontFamily: 'GeosansLight', color: 'white'}} />
</View>
<View style={{ maxHeight: 580, marginTop: 50 }} >
<FlatList
data={this.props.users}
renderItem={this.renderItem}
/>
</View>
</View>
</ImageBackground>
)
}
}
I usually use hammer.js for any swipe interactions I need to manage. Essentially this library just has you create custom event listeners for things like swiping, panning, pinching, rotating, etc. and just attach them to whatever element you'd like. As long as you have a reference to the element, it shouldn't matter if it is a react element or not, it should work just the same.
In React Native, how do you change the style of a textInput when it gets focus? Say I have something like
class MyInput extends Component {
render () {
return <TextInput style={styles.textInput} />;
}
};
const stylesObj = {
textInput: {
height: 50,
fontSize: 15,
backgroundColor: 'yellow',
color: 'black',
}
};
const styles = StyleSheet.create(stylesObj);
And I want to change the background color on focus to green.
This documentation leads me to believe that the solution is something like
class MyInput extends Component {
constructor (props) {
super(props);
this.state = {hasFocus: false};
}
render () {
return (<TextInput
style={this.state.hasFocus ? styles.focusedTextInput : styles.textInput}
onFocus={this.setFocus.bind(this, true)}
onBlur={this.setFocus.bind(this, false)}
/>);
}
setFocus (hasFocus) {
this.setState({hasFocus});
}
};
const stylesObj = {
textInput: {
height: 50,
fontSize: 15,
backgroundColor: 'yellow',
color: 'black',
}
};
const styles = StyleSheet.create({
...stylesObj,
focusedTextInput: {
...stylesObj,
backgroundColor: 'green',
}
});
Ignoring potential mistakes in the styles structuring, would this be considered correct way to handle it? It seems very verbose to me.
You can achieve this by passing in the onFocus and onBlur events to set and unset styles when focused and blurred:
onFocus() {
this.setState({
backgroundColor: 'green'
})
},
onBlur() {
this.setState({
backgroundColor: '#ededed'
})
},
And then, in the TextInput do this:
<TextInput
onBlur={ () => this.onBlur() }
onFocus={ () => this.onFocus() }
style={{ height:60, backgroundColor: this.state.backgroundColor, color: this.state.color }} />
I've set up a full working project here. I hope this helps!
https://rnplay.org/apps/hYrKmQ
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
TextInput
} = React;
var SampleApp = React.createClass({
getInitialState() {
return {
backgroundColor: '#ededed',
color: 'white'
}
},
onFocus() {
this.setState({
backgroundColor: 'green'
})
},
onBlur() {
this.setState({
backgroundColor: '#ededed'
})
},
render: function() {
return (
<View style={styles.container}>
<TextInput
onBlur={ () => this.onBlur() }
onFocus={ () => this.onFocus() }
style={{ height:60, backgroundColor: this.state.backgroundColor, color: this.state.color }} />
</View>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
marginTop:60
}
});
AppRegistry.registerComponent('SampleApp', () => SampleApp);
The best way to control the style when the element is focused / blurred is to create your own TextInput wrapper
export const MyAppTextInput = (props) => {
return (
<TextInput
{...props}
/>
);
};
Note that {...props} will pass in any property you already set or available for TextInput.
Then extend the above component by adding state to apply styles when focus / blur
export const MyAppTextInput = (props) => {
const [isFocused, setIsFocused] = useState(false);
return (
<TextInput
{...props}
style={[props.style, isFocused && {borderWidth: 5, borderColor: 'blue'}]}
onBlur={() => setIsFocused(false)}
onFocus={() => setIsFocused(true)}
/>
);
};
And remember when you use the component to bind the value like in the example (see value={passwordText}); otherwise the value will disappear on blur as a new render commences after the state change.
<MyAppTextInput
style={styles.input}
value={passwordText}
textContentType="password"
autoCompleteType="off"
secureTextEntry
onChangeText={text => {
setPasswordText(text);
}}
/>
You can of course avoid creating a wrapper but if you have more than one input it will create a mess in your input(s) parent components as you will have to add repeating logic.
Use refs, DirectManipulation and setNativeProps for more performance: https://facebook.github.io/react-native/docs/direct-manipulation.
class MyInput extends Component {
focusedInput = () => {
this.textInput.setNativeProps({
style: { backgroundColor: 'green' }
})
}
blurredInput = () => {
this.textInput.setNativeProps({
style: { backgroundColor: 'yellow' }
})
}
render () {
return <TextInput
ref={c => { this.textInput = c}}
style={styles.textInput}
onFocus={this.focusedInput}
onBlur={this.blurredInput} />
}
}
const stylesObj = {
textInput: {
height: 50,
fontSize: 15,
backgroundColor: 'yellow',
color: 'black',
}
}
const styles = StyleSheet.create(stylesObj)
This updates the TextInput component directly without re-rendering the component hierarchy.
You can create a state to keep track of the input-state and use that state to toggle the style. Here is a simple example
const App = () => {
const [isActive, setActive] = useState(false);
return (
<TextInput style={{ color: isActive ? 'black' : 'grey' }} onFocus={() => setActive(true)} onBlur={() => setActive(false)}/>
);
}
Nader Dabit´s pointed me to do something similar -- using the state for styles -- but I think it can be done in a cleaner way if you created separate styles for the focused and unfocused style and pass only the style ID as follows:
getInitialState() {
return { style: styles.textinput_unfocused }
}
onFocus() {
this.setState({ style: styles.textinput_focused })
}
onBlur() {
this.setState({ style: styles.textinput_unfocused })
}
in render -- referencing by styleID in this.state.style, note how the different styles are passed as an Array:
<TextInput
onBlur={ () => this.onBlur() }
onFocus={ () => this.onFocus() }
style={ [styles.textinput, this.state.style] } />
+ your stylesheet à la carte:
textinput_focused: {
backgroundColor: 'red',
color: 'white'
}
textinput_unfocused: {
backgroundColor: 'green'
}
Hey guys I kinda used everyones idea :p
#Felix gave me an idea that might be perhaps even cleaner. (I would have loved to not have included state though on this static component, just to change styling... But I am to new to this to figure that out.
here was my solution:
import React, { Component } from 'react';
import { StyleSheet, TextInput } from 'react-native';
class TxtInput extends Component {
constructor(props) {
super(props);
this.state = {
style: {},
};
}
onFocus = () => {
const state = { ...this.state };
state.style = {
borderStyle: 'solid',
borderColor: '#e74712',
};
this.setState(state);
}
onBlur = () => {
console.log('on ONBLUR')
const state = { ...this.state };
state.style = {};
this.setState(state);
}
render = () => <TextInput style={[styles.input, this.state.style]} onFocus={() => this.onFocus()} onBlur={() => this.onBlur()} />;
}
const styles = StyleSheet.create({
input: {
color: '#000000',
fontFamily: 'MuseoSans 700 Italic',
fontSize: 22,
borderRadius: 34,
borderStyle: 'solid',
borderColor: 'transparent',
borderWidth: 5,
backgroundColor: '#ffffff',
textAlign: 'center',
width: '25%',
},
});
export default TxtInput;
I added the style into an array, have all the actual input styling done on the first property of the array and the second one the nit picking of the focus and blue.
Hope it helps
For that propose I design this simple logic in functional component (it works the same in class components with the pertinents changes), it can be apply to several <textinputs/>. Below I leave an example:
// state
const [isFocused, setIsFocused] = useState({
name: false,
email: false,
phone: false,
})
// handlers
const handleInputFocus = (textinput) => {
setIsFocused({
[textinput]: true
})
}
const handleInputBlur = (textinput) => {
setIsFocused({
[textinput]: false
})
}
// JSX
<View style={styles.form} >
<TextInput
style={isFocused.name ? [styles.inputs, { borderColor: 'blue' }] : styles.inputs}
placeholder='Name'
placeholderTextColor={darkColors.text}
textContentType='name'
keyboardType='default'
onFocus={() => handleInputFocus('name')}
onBlur={() => handleInputBlur('name')}
/>
<TextInput
style={isFocused.email ? [styles.inputs, { borderColor: 'blue' }] : styles.inputs}
placeholder='Email'
placeholderTextColor={darkColors.text}
textContentType='emailAddress'
keyboardType='email-address'
onFocus={() => handleInputFocus('email')}
onBlur={() => handleInputBlur('email')}
/>
<TextInput
style={isFocused.phone ? [styles.inputs, { borderColor: 'blue' }] : styles.inputs}
placeholder='Phone'
placeholderTextColor={darkColors.text}
keyboardType='phone-pad'
onFocus={() => handleInputFocus('phone')}
onBlur={() => handleInputBlur('phone')}
/>
</View>
<TextInput
style={{ backgroundColor: 'white', height: 40, width: 100, alignItems: 'center'
}}
theme={{ colors: { placeholder: 'white', text: 'white', primary: 'white',
underlineColor: 'transparent', background: '#003489' } }}
/>
set initial value in function component
const [warnColor, setWrnColor] = useState("grey");
set this in text input
style={[styles.brdColor, { borderColor: warnColor }]}
set this in stylesheet
brdColor: {
height: 40,
borderColor: "grey",
borderBottomWidth: 1,
marginTop: heightToDp("2%"),
width: "100%",
}
Change TextInput border color on focus, by using react hooks useState and useCallback.
const [isFocused, setIsFocused] = useState(false);
const handleFocus = useCallback(() => {
setIsFocused(!isFocused);
}, [isFocused]);
let bcOnFocus = {
borderColor: isFocused ? '#000000' : '#cccccc',
};
<ScrollView>
<View>
<TextInput
placeholder="Text Field"
style={bcOnFocus}
onFocus={handleFocus}
/>
</View>
</ScrollView>