I have noticed some strange behavior in my test app. My components with "componentWillReceiveProps" function are double call. Should be call only once after button click but it's double in weird order.
I have 3 components:
Test - starting component
SetMessage - Receive props from Test and pass to Animation component
Animation - Receive props from SetMessage and dispaly
So after click button components and functions should be call like this:
Test->SetMessage(functions:reciveProps ->setMsg) then
Animation(functions: reciveProps->showMsg).
But in my case is:
Test->SetMessage(function:reciveProps) then Animation(function:reciveProps->showMsg) then
SetMessage(function: changeMsg) then
Animation(functions: reciveProps->showMsg).
I would like to know, if it is normal and fine? If not, why it happens and how to fix it?
Bellow all code and logs screen.
Index.android.js:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
Navigator,
View
} from 'react-native';
import Test from './app/components/Test/Test';
export default class testApp extends Component {
render(){
return(
<View style={{flex:1}}>
<Test/>
</View>
)
}
}
AppRegistry.registerComponent('testApp', () => testApp);
Test.js:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
Button,
View,
Animated,
Easing,
Switch,
} from 'react-native';
import SetMessage from '../SetMessage/SetMessage';
export default class Test extends Component{
constructor(){
super();
this.state = {
sendMsg:'plus'
}
}
change(){
if(this.state.sendMsg==='plus'){
this.setState({sendMsg:'minus'});
}else{
this.setState({
sendMsg:'minus'
});
}
console.log('Test com ')
}
render(){
return (
<View>
<Button
onPress={this.change.bind(this)}
title={'Start'}
/>
<SetMessage msg={this.state.sendMsg}/>
</View>
)
}
}
AppRegistry.registerComponent('Test', () => Test);
SetMessage.js:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
Button,
View,
Animated,
Easing,
Switch,
} from 'react-native';
import Animation from '../Animation/Animation';
export default class SetMessage extends Component{
constructor(){
super();
this.state = {
test:'',
sendMsg:''
}
}
componentWillReceiveProps(nextProps){
this.setState({
test:nextProps.msg
},()=>this.setMsg());
console.log('SetMessage F - ReciveProp'+this.state.sendMsg)
}
setMsg(){
console.log('SetMessage F - Change Msg '+this.state.sendMsg);
this.setState({
sendMsg:this.state.test
})
}
render(){
return (
<View>
<Animation msg={this.state.sendMsg}/>
</View>
)
}
}
AppRegistry.registerComponent('SetMessage', () => SetMessage);
Animation.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Animated,
Easing,
Switch,
} from 'react-native';
export default class Animation extends Component{
constructor(){
super();
this.state = {
msg:'',
bottom: new Animated.Value(-50)
}
}
componentWillReceiveProps(nextProp){
console.log('Animation F - reciveProp'+this.state.msg);
this.setState({
msg:nextProp.msg
},()=>this.showMsg());
}
showMsg(){
console.log('Animation F - showMsg '+this.state.msg);
if(this.state.msg!='') {
Animated.sequence([
Animated.timing( // Animate over time
this.state.bottom, // The animated value to drive
{
toValue: 0,
duration: 500 // Animate to opacity: 1, or fully opaque
}),
Animated.delay(1000),
Animated.timing(this.state.bottom, // The animated value to drive
{
toValue: -50,
duration: 500 // Animate to opacity: 1, or fully opaque
}),
]).start();
}
}
render(){
return (
<View style={styles.mainCont}>
<Animated.View style={{
height:50,
width:100+'%',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
alignItems:'center',
justifyContent:'center',
position:'absolute',
bottom:this.state.bottom,
}}>
<Text style={styles.tekst}>{this.state.msg}</Text>
</Animated.View>
</View>
)
}
}
const styles=StyleSheet.create({
mainCont:{
flex:1,
backgroundColor:'gray'
},
container:{
height:50,
width:100+'%',
backgroundColor:'#000',
alignItems:'center',
justifyContent:'center',
position:'absolute',
bottom:0
}
});
AppRegistry.registerComponent('Animation', () => Animation);
Log screen:
Thank you.
If you check the official documentation: https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
You will notice the statement "Note that React may call this method even if the props have not changed, so make sure to compare the current and next values...". I assume that some call to render is being made on one of the parents which makes a re-render call to be called on your Animation component. Hence the componentwillreceiveprops is being called. I always use this when using the hook above:
componentWillReceiveProps(newprops) {
if(newprops.active === this.props.active) { return }
//if they are different do some stuff
}
}
Related
I want to create a new Text Component through a function called by pressing a button.
import React, { Component } from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
export default class App extends React.Component {
constructor() {
super();
this.clicks = 0;
}
addText() {
return (
// something to add a new Text component to the View
)
}
ButtonPress() {
this.clicks++;
if (this.clicks > 0 && this.clicks % 20 == 0) {
this.addText();
}
}
render() {
return (
<View style={styles.container}>
<Button
title="Click me"
onPress={this.ButtonPress.bind(this)}
/>
</View>
);
}
}
I know it's possible by making an arrow function in the onPress from the button, but I want to do it exclusively from a function.
Is it possible? If so, how?
Any help is appreciated.
Thanks!
Use conditional rendering for the Text.
condition && <Text>{text}</Text>
Example
import React, { Component } from 'react'
import { Text, View, Button } from 'react-native'
export default class index extends Component {
state={
text: '',
textVisible: false
}
toggle=()=>{
const { textVisible } = this.state
this.setState({textVisible: !textVisible, text: 'Add some text here..'})
}
render() {
const { text, textVisible } = this.state
return (
<View style={styles.container}>
{textVisible&&<Text>{text}</Text>}
<Button title='Toggle' onPress={this.toggle} />
</View>
)
}
}
I am trying to make a custom input component with onChangeText but whenever I start typing in the textInput box I get an error. I have checked the code many times and everything looks fine to me.
import React from "react";
import { TextInput, StyleSheet } from "react-native";
const defaultInput = props => (
<TextInput
underlineColorAndroid="transparent"
{...props}
style={[styles.input, props.style]}
/>
)
const styles = StyleSheet.create({
input: {
width: "100%",
borderWidth: 1,
borderColor: "#eee",
padding: 5,
marginTop: 8,
marginBottom: 8
}
});
export default defaultInput;
This is my sub component where I am using my custom component.
import React, { Component } from "react";
import { View, TextInput, Button, StyleSheet } from "react-native";
import DefaultInput from "../UI/DefaultInput/DefaultInput";
const placeInput = props =>(
<DefaultInput
placeholder="Place Name"
value={props.placeName}
onChangeText={props.onChangeText}
/>
)
export default placeInput;
This is the screen where I am using my sub component.
import React, { Component } from 'react'
import { Text, View, TextInput, Button, StyleSheet, ScrollView, Image } from 'react-native'
import { connect } from 'react-redux'
import placeImage from '../../asset/pic.jpg'
import PlaceInput from '../../component/PlaceInput/PlaceInput'
import { addPlace } from '../../store/actions/index'
import { Navigation } from 'react-native-navigation'
// import DefaultInput from '../../component/UI/DefaultInput/DefaultInput'
import HeadingText from '../../component/UI/HeadingText/HeadingText'
import MainText from '../../component/UI/MainText/MainText'
import PickImage from '../../component/PickImage/PickImage'
import PickLocation from '../../component/PickLocation/PickLocation'
class SharePlace extends Component {
state={
placeName:""
}
constructor(props) {
super(props);
this.props.navigator.setOnNavigatorEvent(this.OnNavigatorEvent);
}
placeNameChangedHandler = val => {
this.setState({
placeName: val
});
};
OnNavigatorEvent = (event) => {
console.log(event);
if (event.type === "NavBarButtonPress") {
if (event.id === "sideDrawerToggle") {
this.props.navigator.toggleDrawer({
side: "left"
})
}
}
}
placeAddedHandler = () => {
if(this.state.placeName.trim() !== "")
{
this.props.onAddPlace(this.state.placeName);
}
}
render() {
return (
// <ScrollView contentContainerStyle={styles.conatiner}>
<ScrollView>
<View style={styles.conatiner}>
<MainText>
<HeadingText>Share a place with us!</HeadingText>
</MainText>
<PickImage />
<PickLocation />
<PlaceInput
placeName={this.state.placeName}
onChangeText={this.placeNameChangedHandler}
/>
<View style={styles.button}>
<Button title="share the place" onPress={this.placeAddedHandler} />
</View>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
conatiner: {
flex: 1,
alignItems: "center"
},
placeholder: {
borderWidth: 1,
borderColor: "black",
backgroundColor: "#eee",
width: "80%",
height: 150
},
button: {
margin: 8
},
imagePreview: {
width: "100%",
height: "100%"
}
})
const mapDispatchToProps = dispatch => {
return {
onAddPlace: (placeName) => dispatch(addPlace(placeName))
}
}
export default connect(null, mapDispatchToProps)(SharePlace)
This is the error I am getting.
ExceptionsManager.js:63 Invariant Violation: TextInput(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
This error is located at:
in TextInput (at DefaultInput.js:5)
in defaultInput (at PlaceInput.js:7)
in placeInput (at SharePlace.js:60)
in RCTView (at View.js:60)
in View (at SharePlace.js:54)
in RCTScrollContentView (at ScrollView.js:791)
in RCTScrollView (at ScrollView.js:887)
in ScrollView (at SharePlace.js:53)
in SharePlace (created by Connect(SharePlace))
in Connect(SharePlace) (at Navigation.js:83)
in Provider (at Navigation.js:82)
in _class2 (at renderApplication.js:33)
in RCTView (at View.js:60)
in View (at AppContainer.js:102)
in RCTView (at View.js:60)
in View (at AppContainer.js:122)
in AppContainer (at renderApplication.js:32)
Using typescript,
import {TextInput, TextInputProps, View} from "react-native";
declare a props interface:
interface Props extends TextInputProps {}
// This ensures that all the properties associated with react-native's
// TextInput component, are associated with your custom-input component as well.
decalare your custom-input component:
const CustomInput = (props: Props) => {
<TextInput onChangeText={props.onChangeText} placeholder={props.placeholder} style={[props.style]} />
}
export default CustomInput;
use your custom-input component in LoginScreen.tsx say;
import React from "react";
import CustomInput from "../CustomInput";
const LoginScreen = () => {
const handleChange = (value: string) =>{
....
}
return(
<CustomInput placeholder="email" onChangeText={value => handleChange(value) />
)
your code working fine as the way I ran it, maybe you have problem to import PlaceInput or DefaultInput:
import React, { Component } from 'react';
import { View, Text, FlatList, ScrollView,TextInput, Image,StyleSheet } from 'react-native';
export default class Test extends Component {
constructor(props) {
super(props);
this.state = {
placeName: ""
}
}
placeNameChangedHandler = val => {
this.setState({
placeName: val
});
};
render() {
return (
<View>
<PlaceInput
placeName={this.state.placeName}
onChangeText={this.placeNameChangedHandler}
/>
</View>
);
}
}
const PlaceInput = props => (
<DefaultInput
placeholder="Place Name"
value={props.placeName}
onChangeText={props.onChangeText}
/>
)
const DefaultInput = props => (
<TextInput
underlineColorAndroid="transparent"
{...props}
style={[styles.input, props.style]}
/>
)
const styles = StyleSheet.create({
input: {
width: "100%",
borderWidth: 1,
borderColor: "#eee",
padding: 5,
marginTop: 8,
marginBottom: 8
}
});
I'm trying to navigate from Homescreen to another screen(Test).I have 'HomeScreen.js' below. Once I click on the register button, I get the above mentioned error.
I've been at this for a whole day, but can't seem to get a straight forward answer.
The error is on my 'Homescreen.js' (Attached screenshot error)
Screenshot
Error is pointing to: this.props.navigator.push under the _handleRegisterView function.
HomeScreen.js
import React from 'react';
import {
StyleSheet,
Text,
View,
AsyncStorage,
Component,
TouchableHighlight,
AppRegistry
} from 'react-native';
import Test from './Test';
import { StackNavigator } from 'react-navigation';
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'SpeedHack',
};
_handleRegisterView = () => {
this.props.navigator.push({
title: 'Test',
component: Test,
backButtonTitle: 'Back'
})
//alert('Register button Pressed!.');
}
render() {
return (
<View style={styles.container}>
<TouchableHighlight onPress={this._handleRegisterView}>
<Text style={[styles.button, styles.blueButton]}>
Register
</Text></View>
);
}
}
Test.js (Doesn't really do anything interesting, loads an image)
import React from 'react';
import { Component, StyleSheet, Text, View, Image } from 'react-native';
class Test extends React.Component {
render() {
let pic = {
uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'
};
return (
<View style={styles.container}>
<Text>Ssup Dude! Want some bananas?</Text>
<Image source = {pic} style = {{width: 300, height: 300}}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
You seem to be confused. In react-navigation in order to push a screen, you do not do this.props.navigator.push, you use this.props.navigation.navigate.
I'm not sure how to describe what I'm trying to do with words so please take a look at the following code:
This is what causing me issues: this.fetchMessages()
import React, { Component } from 'react';
import { PushNotificationIOS, FlatList, TextInput, ActivityIndicator, ListView, Text, View, Image, TouchableWithoutFeedback, AsyncStorage } from 'react-native';
import { Actions } from 'react-native-router-flux';
import ConversationsItem from './ConversationsItem';
import { conversationFetch } from '../actions';
import { connect } from 'react-redux';
import { Divider } from 'react-native-elements'
import PushNotification from 'react-native-push-notification';
class Conversations extends Component {
componentDidMount() {
this.props.conversationFetch()
}
fetchMessages() {
this.props.conversationFetch()
}
render() {
PushNotification.configure({
onNotification: function(notification) {
PushNotification.getApplicationIconBadgeNumber((response) => {
PushNotification.setApplicationIconBadgeNumber(response + 1)
})
console.log( 'NOTIFICATION:', notification )
notification.finish(PushNotificationIOS.FetchResult.NoData);
this.fetchMessages()
}
});
if (!this.props.email) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
console.log(this.props.conversations)
return (
<View style={{flex: 1, backgroundColor: 'white'}}>
...
</View>
);
}
}
const mapStateToProps = (state) => {
console.log(state)
const { conversations } = state.conversation;
const { email } = state.conversation;
return { conversations, email };
};
export default connect(mapStateToProps, { conversationFetch })(Conversations);
When I call this.fetchMessages() inside PushNotification.configure({}), I get the following error message:
this.fetchMessages is not a function
I'm not sure if what I'm doing is possible but if so I'd really like to know how to make this work.
I tried adding .bind(this) and other ways around but got same error anyways.
Thanks for you help.
Functions declared with function keyword has their own this, so this inside onNotification function does not refer to the class.
Therefore use arrow function syntax, which will lexically resolve this and value of this inside will refer to class itself. So convert
onNotification: function(notification) {
to
onNotification: (notification) => {
So you have in fact tried binding the fetchMessages function in the constructor? Like such:
constructor(props) {
super(props)
this.fetchMessages = this.fetchMessages.bind(this);
}
You can also use an arrow function to bind your method to the class without calling the constructor like such:
() => this.fetchMessages()
I don't understand how I'm getting this error (pic below). In my LoginForm.js file, the onEmailChange(text) is giving me an unresolved function or method call to onEmailChange() error when I hover over it in my WebStorm IDE. In my index.js file, no error is being thrown anywhere.
I've looked around SO for this issue but it doesn't fully pertain to my problem.
I've tried File > Invalidate Caches/Restart but that didn't work.
Here's App.js:
import React, { Component } from 'react';
import {StyleSheet} from 'react-native';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import firebase from 'firebase';
import reducers from './reducers';
import LoginForm from './components/common/LoginForm';
class App extends Component {
render() {
return(
<Provider style={styles.c} store={createStore(reducers)}>
<LoginForm/>
</Provider>
);
}
}
const styles = StyleSheet.create({
c: {
flex: 1
}
});
export default App;
Here's LoginForm.js:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {emailChanged} from 'TorusTeensApp/src/actions';
import {Text, StyleSheet, KeyboardAvoidingView, TextInput, TouchableOpacity} from 'react-native';
class LoginForm extends Component {
render() {
onEmailChange(text)
{
this.props.emailChanged(text);
}
return(
<KeyboardAvoidingView style={styles.container}>
<TextInput
style={styles.userInput}
onsubmitediting={() => this.passwordInput.focus()}
returnKeyType={"next"}
placeholder={"Email"}
label={"Email"}
keyboardType={"email-address"}
autoCorrect={false}
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
<TextInput
style={styles.userInput}
ref={(userInput) => this.passwordInput = userInput}
returnKeyType={"go"}
placeholder={"Password"}
label={"Password"}
secureTextEntry
/>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>Create Account</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
);
}
}
const styles = StyleSheet.create({
container: {
padding: 20 // creates a gap from the bottom
},
userInput: {
marginBottom: 20,
backgroundColor: '#9b42f4',
height: 40
},
buttonContainer: {
backgroundColor: '#41bbf4',
paddingVertical: 10,
marginBottom: 20
},
buttonText: {
textAlign: 'center',
color: '#FFFFFF'
}
});
const mapStateToProps = state => {
return {
email: state.auth.email
};
};
export default connect(mapStateToProps, null, {emailChanged}) (LoginForm);
Here's index.js:
import {EMAIL_CHANGED} from './types';
export const emailChanged = (text) => {
return {
type: 'EMAIL_CHANGED',
payload: text
};
};
export default emailChanged();
Your connect is miswired
connect(mapStateToProps, null, {emailChanged}) (LoginForm);
It should be something like:
connect(mapStateToProps,
(dispatch) => ({emailChanged: (text) => dispatch(emailChanged(text))})
)(LoginForm);
so that your action actually gets dispatched
and as spotted by emed in comment:
export default emailChanged;
without parentheses.
You defined your callback inside your render() method and not inside the class body. Do it like this:
class LoginForm extends Component {
onEmailChange(text) {
this.props.emailChanged(text);
}
render() {
return(...);
}
}
Also you shouldn't bind methods inside your render() method. Do it in the constructor of your Component:
class LoginForm extends Component {
constructor(props) {
super(props);
this.onEmailChange.bind(this);
}
onEmailChange(text) {
// do something
}
// other methods
}
Or if you use babel and ES6, you can define your callback with an arrow function, then it will be automatically bound:
class LoginForm extends Component {
onEmailChange = text => {
// do something
};
// other methods
}
See also the react docs about autobinding.
Also your call to connect seems incorrect. If you want to dispatch the action emailChanged it has to look like this:
const mapStateToProps = state => {
return {
email: state.auth.email
};
};
const mapDispatchToProps = dispatch => {
// this put a function emailChanged into your props that will dispatch the correct action
emailChanged: text => dispatch(emailChanged(text))
};
const LoginFormContainer = connect(mapStateToProps, mapDispatchToProps)(LoginForm);
export default LoginFormContainer;
The third argument to connect needs to be a function that knows how to merge the output of mapStateToProps, mapDispatchToProps, and ownProps all into one object that is then used as props for your connected component. I think you're trying to pass that action to the mapDispatchToProps argument, which is the second argument not the third. So, based on what I think you're doing, you probably wanna change your connect line to look like this.
export default connect(mapStateToProps, {emailChanged}) (LoginForm);
Then, export the function from your actions file not the output of calling that function.
export default emailChanged;
Notice I removed the parentheses so it's not being called.
Then make the callback function a method on your class and bind it in the constructor.
constuctor(props) {
super(props);
this.onEmailChange = this.onEmailChange.bind(this);
}
onEmailChange(text) {
this.props.emailChanged(text);
}
Then update onChangeText on that element.
onChangeText={this.onEmailChange}