Update title and/or buttons in navbar from within component with react-router-native & react-router-navigation - react-native

I am using react-router-navigation in my app. Let's say we have a login page where user have to enter his username and this username should appear in navbar as user is typing.
Given that routing is defined elsewhere from the actual component and component does not have callbacks to change route props: is it possible at all to achieve this? Perhaps some smart HOC? Ideas?
Hacks like passing the username all the way around with redux or similar are not the option. To be more specific I wish to be able to override any property specified in Card (right button, title, anything) from within component. Is it possible?
Please find sample code below,
Thanks
class Login extends Component {
render() {
return (
<View
style={{
flex: 1,
flexDirection: 'column',
alignItems: 'center',
}}>
<Text>
Username
</Text>
<TextInput style={{
height: 60,
width: 200,
}}
autoFocus
onChangeText={(value) => {console.log('NAME: ', value)}}/>
</View>
)
}
}
const App = () => (
<NativeRouter>
<Navigation>
<Card path="/"
title="Login"
exact
component={Login}
/>
</Navigation>
</NativeRouter>
)

Related

Handle on press in a react-native custom component

I'm using react-native. I need to show a menu that consists of a list of items. I simplified the code below and I'm showing only a text but in the real case, the item has got a checkbox an icon and a label so for this reason I created a custom component.
I need to handle the on click on the items.
The problem is that the on press on the TouchableOpacity in the widget is not triggered.
If I use the same hierarchy of components directly the on click works.
So in the example below the onClick on the TouchableOpacity that is directly put in the containing view is triggered but the onClick on the TouchableOpacity that is wrapped in the EventOnMapMenuWidget is not triggered.
How can I handle the onClick on the TouchableOpacity in the EventOnMapMenuWidget?
Please notice that in the log I don't see the
EventOnMapMenuWidget onPress
while I see the
TEST onPress
private drawEventsOnMapForm() {
return <View style={{ margin: marginStyle.medium, flexDirection: 'column' }}>
<EventOnMapMenuWidget
onToggle={() => {
console.log("EventOnMapMenuWidget onToggle")
}}
label={STRINGS.speeding}
/>
<TouchableOpacity style={{ margin: marginStyle.medium, flexDirection: 'row' }} onPress={() => {
console.log("TEST onPress")
}}>
<Text style={{ color: COLORS.black }}>{STRINGS.speeding}</Text>
</TouchableOpacity>
</View>
}
This is the definition of the custom components:
export default class EventOnMapMenuWidget extends Component<EventOnMapMenuWidgetProp> {
render() {
return <TouchableOpacity style={{ margin: marginStyle.medium, flexDirection: 'row' }} onPress={()=> {
console.log("EventOnMapMenuWidget onPress", this.props.label)
this.props.onToggle()
}}>
<Text style={{color: COLORS.black}}>{this.props.label}</Text>
</TouchableOpacity>
}
}

How to "lock" the position of a component on react native screen?

I am trying to build the profile screen of a user on my social networking app. The way I want it is the top 1/3 of the screen is the user profile information, and the bottom 2/3 is a flatlist of all the user's posts. Currently, I have that all working logic wise. Here is a screenshot from the app:
As you can see, the user profile information is displaying up top, and the user's post history is correctly being displayed in a flat list below. However, the flat list is pushing the user profile information "up" on the screen.
Here is the code for the profile:
class Profile extends React.Component {
constructor(props) {
super(props)
this.state = {
user: this.props.user
}
}
goToSettings = () => {
this.props.navigation.navigate('Settings')
}
render() {
return (
<View style={styles.container}>
<View style={{ flexDirection: "row", padding: 20 }}>
<Text style = {styles.subheader}> {this.state.user.username} </Text> <------The username is being pushed up, where only the bottom half of it is visible
<TouchableOpacity
onPress={this.goToSettings}>
<Ionicons name="ios-settings" size={24} color="black" />
</TouchableOpacity>
</View>
<View style = {styles.lineStyle} />
<View style={{ flexDirection: "row" }}>
<ProfilePic/>
<ProfileStats/>
</View>
<View style = {styles.lineStyle} />
<ProfileBio />
<View style = {styles.lineStyle} />
<View style={{ flexDirection: "row", alignItems: 'center', justifyContent: 'center' }}>
<CurrentUserPostFeed navigation = {this.props.navigation} /> <------ This is the flat list. It is pushing up the views & other components that are above it.
</View>
</View>
)
}
}
I want the position of the user profile information to stay in the same postition, taking up the top 1/3 of the screen, while the user can scroll through the flatlist which only takes up the bottom 2/3 of the screen
However, I am not sure how to "lock" the profile information in place, where it is always visible in the top 1/3 of the profile screen, no matter how big/small the flatlist of posts becomes. Any advice?
EDIT: It seems that the margin/padding in the cells of the flatlist are causing my issue. Is there a way that I can prevent the flatlist container from pushing the elements above it "up" when I add marginTop or paddingTop?
You should use the flex property to set which
specifies how much of the remaining space in the flex container should be assigned to the item
More info about the flex property here.
** flex is a shorthand for flex-grow.
So, you can do something like this:
<View> // main container
<View style={{flex: 1}}> // header container
// Header Content
</View>
<View style={{flex: 2}}> // flatlist container
// Flatlist content
</View>
</View>
Basically what we are doing here is setting the header to use 1/3 of the available space of main-container and flatlist to use 2/3 of the available sapce of the main-container.

React Native Autocomplete Input results list ignores touch

I've been tackling this issue for a few weeks, and it's driving me insane.
Basically, I have a Modal component that nests a form. The form is a bunch of TextInput components, at the heart of everything. One of the components in the form is an Autocomplete, from React Native Autocomplete Input. The problem is that I'm able to put the results list from Autocomplete in front of everything else, but my touches pass right through the container and focuses on the TextInput behind the results list. I'm not able to change the order of components, so I can't just put this one input after everything else.
The general setup of the form is below:
<Modal>
<TouchableWithoutFeedback>
<View style={containerStyle}>
<TouchableWithoutFeedback>
<View>
<CardSection style={sectionStyle}>
<Input
...props...
/>
</CardSection>
<CardSection style={acSectionStyle}>
<Text style={labelStyle}>Brand *</Text>
<View style={acContainerStyle}>
<Autocomplete
autoCapitalize='none'
autoCorrect={false}
listStyle={acListStyle}
data={brands.length === 1 && comp(query, brands[0]) ? [] : brands}
defaultValue={query}
onChangeText={text => this.setState({ query: text })}
renderItem={this.renderItem.bind(this)}
hideResults={this.state.hideResults ? this.state.hideResults : undefined}
onBlur={() => this.setState({ hideResults: true })}
onFocus={() => this.setState({ hideResults: false })}
/>
</View>
</CardSection>
<CardSection style={sectionStyle}>
<Input
...props...
/>
</CardSection>
</View>
</TouchableWithoutFeedback>
</View>
</TouchableWithoutFeedback>
</Modal>
I had to stack the TouchableWithoutFeedback components in order to make the modal behave. There's more props in the components, but I only kept what was relevant.
My renderItem method is:
renderItem(brand) {
return (
<TouchableOpacity
style={{ width: '100%', height: 25 }}
onPress={() => {
this.setState({ pBrand: brand.trim(), query: brand.trim() });
}}
>
<Text style={styles.listItemStyle}>{brand}</Text>
</TouchableOpacity>
);
}
I don't believe it's a styling issue, but I've added the styles that deal with zIndex just in case:
containerStyle: {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
position: 'relative',
flex: 1,
justifyContent: 'center',
zIndex: 1
},
acSectionStyle: {
justifyContent: 'center',
zIndex: 2,
height: 40
},
acContainerStyle: {
right: 0,
width: '75%',
flex: 1,
position: 'absolute',
zIndex: 2
}
The default keyboardShouldPersistTaps for Autocomplete is always. All of the questions I've seen suggest to set a higher zIndex (which isn't a problem - I can see the list, but if I tap on it, the tap goes through to the TextInput behind it), change the order of components (which I can't do), set onStartShouldSetResponderCapture to true (which didn't work), or mess with Pointer Events, none of which worked.
I'm using React Native V0.57.1, an actual Android device, and the project is built with Expo.
Finally, I've recorded a small demo for what my problem is. When the cursor re-appears, that's when I clicked on a result.
Is there just something I'm missing? I've only been writing in React Native for a few months so that's a definite possibility. I come from a web development background, so I thought that if a component was on top (thanks to zIndex), I'd be able to tap on it and not through it by default.
Edit: While messing around, if I change acSectionStyle to a height big enough for the dropdown, then the dropdown works how it should. The issue comes in when a sibling CardSection is being covered. The other CardSection takes precedence.
So, I finally found a workaround. Whether it's correct or not, which I feel like it isn't, at least it works!
I ended up taking the Autocomplete component (along with its' view) outside of the CardSection, but leaving the label in it, like so:
<CardSection style={acSectionStyle}>
<Text style={labelStyle}>Brand *</Text>
</CardSection>
<View style={acContainerStyle}>
<Autocomplete
autoCorrect={false}
listStyle={acListStyle}
// Text input container
inputContainerStyle={acTextContainerStyle}
style={acTextStyle}
placeholder='China Glaze'
data={brands.length === 1 && comp(query, brands[0]) ? [] : brands}
defaultValue={query}
onChangeText={text => this.setState({ query: text })}
renderItem={this.renderItem.bind(this)}
hideResults={this.state.hideResults ? this.state.hideResults : undefined}
onBlur={() => this.setState({ hideResults: true })}
onFocus={() => this.setState({ hideResults: false })}
/>
</View>
Then, and this is the part I think is wrong, I just played with the absolute-positioned view until I moved it far enough down to line up next to the label:
labelStyle: {
fontSize: 18,
paddingLeft: 20,
flex: 1
},
acContainerStyle: {
right: 0,
top: 102,
width: '72%',
flex: 1,
position: 'absolute',
zIndex: 10
}
I would love if someone has a better solution, but this is the best I could come up with. I try to avoid moving views with hard coded values just for scaling purposes, but it seems like this is the only option.

Navigating to the first component in a stack navigator with params doesn't work

I have a component within a StackNavigator that I always want to pass params to. This doesn't seem to work if my component is the first component loaded in a StackNavigator. Here's some code that illustrates the problem.
I start in component A.
I press the button to navigate to component B.
In the logs you can see that component B is loaded twice. The first time it's loaded without params.
Android SDK built for x86: rendering with: undefined
Android SDK built for x86: rendering with: ►{data:"from A"}
Also if you click the device back button it goes back to the screen with undefined params.
So there are two issues:
I only navigated once, but I have to go back twice to get back to where I was.
My component is loaded with no params even though I navigated with params.
Here's the code running in an expo snack: https://snack.expo.io/#bdzim/navigate-to-tab-from-parent
class A extends React.Component {
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="This is A. Press to go to B"
onPress={() => this.props.navigation.navigate(
'B', { data: 'from A' }
)}
/>
</View>
);
}
}
class B extends React.Component {
render() {
console.log('rendering with: ', this.props.navigation.state.params)
let params = JSON.stringify(this.props.navigation.state.params)
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>
params: {params}
</Text>
<Button
title="This is.. B! Press to go to A"
onPress={() => this.props.navigation.navigate('A')}
/>
</View>
);
}
}
const Stacks = StackNavigator({ B })
const Tabs = TabNavigator({ A, Stacks });

setNativeProps Change Value for Text Component React Native Direct Manipulation

I want to directly update the value of a component due to performance reasons.
render(){
<View>
<Text style={styles.welcome} ref={component => this._text = component}>
Some Text
</Text>
<TouchableHighlight underlayColor='#88D4F5'
style={styles.button}>
<View>
<Text style={styles.buttonText}
onPress={this.useNativePropsToUpdate.bind(this)}>
Iam the Child
</Text>
</View>
</TouchableHighlight>
</View>
}
This is the method I use to update the text component. I dont know if I am setting the right attribute/ how to figure out which attribute to set:
useNativePropsToUpdate(){
this._text.setNativeProps({text: 'Updated using native props'});
}
Essentially trying to follow the same approach from this example:
https://rnplay.org/plays/pOI9bA
Edit:
When I attempt to explicitly assign the updated value:
this._text.props.children = "updated";
( I know this this the proper way of doing things in RN ). I get the error "Cannot assign to read only property 'children' of object'#'"
So maybe this is why it cant be updated in RN for some reason ?
Instead of attempting to change the content of <Text> component. I just replaced with <TextInput editable={false} defaultValue={this.state.initValue} /> and kept the rest of the code the same. If anyone know how you can change the value of <Text> using setNativeProps OR other method of direct manipulations. Post the answer and ill review and accept.
The text tag doesn't have a text prop, so
this._text.setNativeProps({ text: 'XXXX' })
doesn't work.
But the text tag has a style prop, so
this._text.setNativeProps({ style: { color: 'red' } })
works.
We can't use setNativeProps on the Text component, instead, we can workaround and achieve the same result by using TextInput in place of Text.
By putting pointerEvent='none' on the enclosing View we are disabling click and hence we can't edit the TextInput (You can also set editable={false} in TextInput to disbale editing)
Demo - Timer (Count changes after every 1 second)
import React, {Component} from 'react';
import {TextInput, StyleSheet, View} from 'react-native';
class Demo extends Component {
componentDidMount() {
let count = 0;
setInterval(() => {
count++;
if (this.ref) {
this.ref.setNativeProps({text: count.toString()});
}
}, 1000);
}
render() {
return (
<View style={styles.container} pointerEvents={'none'}>
<TextInput
ref={ref => (this.ref = ref)}
defaultValue={'0'}
// editable={false}
style={styles.textInput}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 0.7,
justifyContent: 'center',
alignItems: 'center',
},
textInput: {
fontSize: 60,
width: '50%',
borderColor: 'grey',
borderWidth: 1,
aspectRatio: 1,
borderRadius: 8,
padding: 5,
textAlign: 'center',
},
});
export default Demo;
As setNativeProps not solving the purpose to alter the content of <Text />, I have used below approach and is working good. Create Simple React Component like below...
var Txt = React.createClass({
getInitialState:function(){
return {text:this.props.children};
},setText:function(txt){
this.setState({text:txt});
}
,
render:function(){
return <Text {...this.props}>{this.state.text}</Text>
}
});