React Native Navigation - Action using Component's State - react-native

I've made a full-screen TextInput and would like to have an action performed when the Post button in the NavigationBar is pressed. However, because I have to make the method that the Button is calling in the onPress prop a static method, I don't have access to the state.
Here is my current code, and the state comes up undefined in the console.log.
import React, { Component } from 'react';
import { Button, ScrollView, TextInput, View } from 'react-native';
import styles from './styles';
export default class AddComment extends Component {
static navigationOptions = ({ navigation }) => {
return {
title: 'Add Comment',
headerRight: (
<Button
title='Post'
onPress={() => AddComment.postComment() }
/>
),
};
};
constructor(props) {
super(props);
this.state = {
post: 'Default Text',
}
}
static postComment() {
console.log('Here is the state: ', this.state);
}
render() {
return (
<View onLayout={(ev) => {
var fullHeight = ev.nativeEvent.layout.height - 80;
this.setState({ height: fullHeight, fullHeight: fullHeight });
}}>
<ScrollView keyboardDismissMode='interactive'>
<TextInput
multiline={true}
style={styles.input}
onChangeText={(text) => {
this.state.post = text;
}}
defaultValue={this.state.post}
autoFocus={true}
/>
</ScrollView>
</View>
);
}
}
Any ideas how to accomplish what I'm looking for?

I see you've found the solution. For future readers:
Nonameolsson posted how to achieve this on Github:
In componentDidMount set the method as a param.
componentDidMount () {
this.props.navigation.setParams({ postComment: this.postComment })
}
And use it in your navigationOptions:
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state
return {
title: 'Add Comment',
headerRight: (
<Button
title='Post'
onPress={() => params.postComment()}
/>
),
};
};

Kinda like a hack but i use the global variable method where we assign this to a variable call foo. Works for me.
let foo;
class App extends Component {
static navigationOptions = ({ navigation }) => {
return {
title: 'Add Comment',
headerRight: (
<Button
title='Post'
onPress={() => foo.postComment() } <- Use foo instead of this
/>
),
};
};
componentWillMount() {
foo = this;
}
render() {
return (<div>Don't be a foo</div>)
}
}

Related

Update props from other component in react native

I have a Main class which I show an array to user, then in detail page user can edit each element which I'm passing using react navigation parameter. I want to edit my array in the detail class and save it using async storage.
//Main.jsimport React from 'react';
import {
StyleSheet ,
Text,
View,
TextInput,
ScrollView,
TouchableOpacity,
KeyboardAvoidingView,
AsyncStorage
} from 'react-native'
import Note from './Note'
import detail from './Details'
import { createStackNavigator, createAppContainer } from "react-navigation";
export default class Main extends React.Component {
static navigationOptions = {
title: 'To do list',
headerStyle: {
backgroundColor: '#f4511e',
},
};
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
dueDate: ''
};
}
async saveUserTasks(value) {
try {
await AsyncStorage.setItem('#MySuperStore:userTask',JSON.stringify(value));
} catch (error) {
console.log("Error saving data" + error);
}
}
getUserTasks = async() =>{
try {
const value = await AsyncStorage.getItem('#MySuperStore:userTask');
if (value !== null){
this.setState({ noteArray: JSON.parse(value)});
}
} catch (error) {
console.log("Error retrieving data" + error);
}
}
render() {
this.getUserTasks()
let notes = this.state.noteArray.map((val,key) => {
return <Note key={key} keyval={key} val={val}
deleteMethod={ () => this.deleteNote(key)}
goToDetailPage= {() => this.goToNoteDetail(key)}
/>
});
const { navigation } = this.props;
return(
<KeyboardAvoidingView behavior='padding' style={styles.keyboard}>
<View style={styles.container}>
<ScrollView style={styles.scrollContainer}>
{notes}
</ScrollView>
<View style={styles.footer}>
<TextInput
onChangeText={(noteText) => this.setState({noteText})}
style={styles.textInput}
placeholder='What is your next Task?'
placeholderTextColor='white'
underlineColorAndroid = 'transparent'
>
</TextInput>
</View>
<TouchableOpacity onPress={this.addNote.bind(this)} style={styles.addButton}>
<Text style={styles.addButtonText}> + </Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
}
addNote(){
if (this.state.noteText){
var d = new Date();
this.state.noteArray.push({
'creationDate': d.getFullYear() + "/" + (d.getMonth()+1) + "/" + d.getDay(), 'taskName': this.state.noteText,'dueDate':'YYYY/MM/DD'
});
this.setState({noteArray:this.state.noteArray})
this.setState({noteText: ''});
this.saveUserTasks(this.state.noteArray)
}
}
deleteNote(key){
this.state.noteArray.splice(key,1);
this.setState({noteArray: this.state.noteArray})
this.saveUserTasks(this.state.noteArray)
}
goToNoteDetail=(key)=>{
this.props.navigation.navigate('DetailsScreen', {
selectedTask: this.state.noteArray[key],
});
}
}
in detail view I have this method which is similar to add note in main class:
export default class Details extends React.Component {
render() {
const { navigation } = this.props;
const selectedTask = navigation.getParam('selectedTask', 'task');
return(
<View key={this.props.keyval} style={styles.container}>
<TouchableOpacity onPress={this.saveEdit.bind(this)} style={styles.saveButton}>
<Text style={styles.saveButtonText}> save </Text>
</TouchableOpacity>
</View>
);
}
saveEdit(){
let selectedItem = { 'creationDate': selectedTask['creationDate'],
'taskName': selectedTask['taskName'],
'dueDate': this.state.dueData}
this.props.navigation.state.params.saveEdit(selectedItem)
}
}
How can I change my props in any component?
First of all you shouldn't call this.getUserTasks() in the render method because the function has this.setState which is bad and could end in a endless loop I guess or at least effect in worse performance. You could instead call it in componentDidMount:
componentDidMount = () => {
this.getUserTasks();
}
Or alternatively call already in constructor but I prefer the first option:
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
dueDate: ''
};
this.getUserTasks()
}
this.props.noteArray.push({.. is probably undefined because you aren't passing it down any where. (Didn't see any reference in your snippet). I guess I would implement the saveEdit function in the Main.js component and simply pass it down to the navigation route and call the function in Details component by accessing the navigation state props:
Update
goToNoteDetail=(key)=>{
this.props.navigation.navigate('DetailsScreen', {
// selectedTask: this.state.noteArray[key],
selectedItem: key,
saveEdit: this.saveEdit
});
}
saveEdit(selectedItem){
const selectedTask = this.state.noteArray[selectedItem]
this.state.noteArray.push({
'creationDate': selectedTask['creationDate'],
'taskName': selectedTask['taskName'],
'dueDate': this.state.dueData
});
this.setState({noteArray:this.state.noteArray})
this.setState({dueData: 'YYYY/MM/DD'});
this.saveUserTasks(this.state.noteArray)
}
And then call saveEdit in Details Component:
saveSelectedItem = () => {
const { navigation } = this.props.navigation;
const {selectedItem, saveEdit} = navigation.state && navigation.state.params;
saveEdit(selectedItem)
}

Update value in StackNavigation custom header

I defined a custom view as a component for headerRight property in navigationOptions as bellow:
static navigationOptions = ({ navigation }) => {
return {
headerRight: navigation.getParam('headerRight', null),
};
};
and then in componentDidMount:
this.props.navigation.setParams({
headerRight: (<MessageDetailsHeader {...this.props}
title = {this.state.headerTitle}
subTitle = {this.state.headerSubTitle}
online = {this.state.online}
/>)
})
also i defined some state for updating values:
constructor(props) {
super(props);
this.state = {
headerTitle: null,
headerSubTitle: null,
headerOnline: false
};
}
Custom header view component defined as bellow:
export default class MessageDetailsHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
title: this.props.title,
subTitle: this.props.subTitle,
online: this.props.online
};
}
componentWillReceiveProps(nextProps) {
this.setState({
title: nextProps.title,
subTitle: nextProps.subTitle,
online: nextProps.online,
})
}
render() {
return (
<View style={styles.headerContainer}>
<View style={styles.headerDetailsContainer}>
<Text style={styles.headerTitle}>{this.state.title}</Text>
<Text style={styles.headerSubTitle}>{this.state.subTitle}</Text>
</View>
<Avatar small rounded source={require('../images/no-profile.png')} activeOpacity={0.7} avatarStyle={this.state.online? styles.avatarOnline: styles.avatarOffline}/>
</View>
);
}
}
I need to call setState in screen and then update Custom View in navigation header, is this a right way?
Thanks in advance
Solved!
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
headerRight: <MessageDetailsHeader
title={params.headerTitle}
subTitle={params.headerSubTitle}
online={params.headerOnline}
/>
};
};
and then called bellow code to set new value, easily!
this.props.navigation.setParams({
headerSubTitle: 'online',
});

React Navigation - Setting params without navigating to the scene

I want to add a 'reload' button to my header, which I'm doing by calling setParams. Problem is that this causes the TabNavigator to navigate to the tab at launch
export default class FeedScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
if (params) {
return {
headerRight: <Icon.Button
color="black"
backgroundColor="transparent"
underlayColor="transparent"
name="reload"
onPress={ params.reload }
/>
};
}
}
constructor (props) {
super(props);
// We want to set attach a reload function to the navigation
this.props.navigation.setParams({
reload: this._reloadFeed.bind(this)
});
}
So is there a way setParams can not navigate to this scene, or is there a way to assign the function to this icon without calling setParams?
Have you tried setting onPress={ params.reload } to onPress={() => params.reload()}?
export default class FeedScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
if (params) {
return {
headerRight: <Icon.Button
color="black"
backgroundColor="transparent"
underlayColor="transparent"
name="reload"
onPress={ () => params.reload() } < -----
/>
};
}
}
constructor (props) {
super(props);
// We want to set attach a reload function to the navigation
this.props.navigation.setParams({
reload: this._reloadFeed.bind(this)
});
}
You are probably looking for Navigation Actions, specifically Navigation Action that is called "setParams" (do not confuse with navigation.setParams()), which you dispatch this way: navigation.dispatch(myCreatedSetParamsAction)
I was able to work around this by calling a static function from the header, and just updating that static function to my reload function in the componentDidMount. It's hacky but works.
Function outside my component:
let _reloadFunction = () => {
// This exists because react navigation is a mess
}
Navigation options
static navigationOptions = {
headerRight: <Icon.Button
color="black"
backgroundColor="transparent"
underlayColor="transparent"
name="reload"
onPress={() => { _reloadFunction() }}
/>
}
In the component:
// Gonna update that reloadFunction so the header works
_reloadFunction = this._reloadFeed.bind(this);

React Native React Navigation Header Button Event

Hello I 'm trying to bind a function in my Navigator Right Button,
But It gives error.
This is my code:
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/FontAwesome';
import Modal from 'react-native-modalbox';
import { StackNavigator } from 'react-navigation';
import {
Text,
View,
Alert,
StyleSheet,
TextInput,
Button,
TouchableHighlight
} from 'react-native';
import NewsTab from './tabs/news-tab';
import CustomTabBar from './tabs/custom-tab-bar';
export default class MainPage extends Component {
constructor(props) {
super(props);
}
alertMe(){
Alert.alert("sss");
}
static navigationOptions = {
title: 'Anasayfa',
headerRight:
(<TouchableHighlight onPress={this.alertMe.bind(this)} >
<Text>asd</Text>
</TouchableHighlight>)
};
render() {
return(
<View>
</View>
);
}
}
And Get error like this:
undefined is not an object (evaluating 'this.alertMe.bind')
When I use this method in render function it is working great but in NavigatonOption I cant get handled it. what can I do for this problem.
You should use this in you navigator function
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
title: '[ Admin ]',
headerTitleStyle :{color:'#fff'},
headerStyle: {backgroundColor:'#3c3c3c'},
headerRight: <Icon style={{ marginLeft:15,color:'#fff' }} name={'bars'} size={25} onPress={() => params.handleSave()} />
};
};
use the componentwillmount so that it can represent where you are calling function .
componentDidMount() {
this.props.navigation.setParams({ handleSave: this._saveDetails });
}
and then you can write your logic in the function
_saveDetails() {
**write you logic here for **
}
**no need to bind function if you are using this **
May be same as above ...
class LoginScreen extends React.Component {
static navigationOptions = {
header: ({ state }) => ({
right: <Button title={"Save"} onPress={state.params.showAlert} />
})
};
showAlert() {
Alert.alert('No Internet',
'Check internet connection',
[
{ text: 'OK', onPress: () => console.log('OK Pressed') },
],
{ cancelable: false }
)
}
componentDidMount() {
this.props.navigation.setParams({ showAlert: this.showAlert });
}
render() {
return (
<View />
);
}
}
react-navigation v5 version would be:
export const ClassDetail = ({ navigation }) => {
const handleOnTouchMoreButton = () => {
// ...
};
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<TouchableOpacity onPress={handleOnTouchMoreButton}>
<Icon name="more" />
</TouchableOpacity>
),
});
}, [navigation]);
return (
// ...
)
}
This is for navigation v4. You need to modify navigationoptions outside of the functional component. Your button event needs to pass a param via navigation.
pageName['navigationOptions'] = props => ({
headerRight: ()=>
<TouchableOpacity onPress={() => props.navigation.navigate("pageRoute",
{"openAddPopover": true}) } ><Text>+</Text></TouchableOpacity> })
and then in your functional component, you can use that param to do something like this:
useLayoutEffect(() => {
doSomethingWithYourNewParamter(navigation.getParam("openAddPopover"))
}, [navigation])

How to dynamically change the header title text in a react native app?

I have been struggling with something that sounds simple. I have been trying to get button when onpressed is called. Have tried several combinators but nothing works. Any ideas?
export default class JobScreen extends React.Component {
constructor(props) {
super(props);
}
static navigationOptions = ({navigation}) => ({
title: "Chat with"
});
onPressLearnMore(nav) {
// how do I update the title header
console.log("On Pressed" + nav)
}
render() {
const {navigate} = this.props.navigation;
return (
<View style={styles.container}>
<Button
onPress={() => {this.onPressLearnMore(navigate)}}
title="Learn More"
color="#841584"
/>
</View>
);
}
}
Since the navigationOptions has access to the navigation object, you can set some param on the current screen with this.props.navigation.setParam({ title: ‘some title’ }) and then access it in navigationOptions like this
static navigationOptions = ({ navigation }) => {
const { state: { params = {} } } = navigation;
return {
title: params.title || ‘default title’,
};
}