Reputation: 584
I am trying to create a login module. I have a LoginView, which defines the view and a LoginController where I have defined all the user interactions. Now I am trying to incorporate a logic where in the LoginController will change the state of LoginView, as in change the value of isLoading from false to true in case all the input data is valid
LoginView
import React, { Component, Fragment} from 'react';
import LoginController from '../Controller/LoginController.js';
import {
View,
ScrollView,
StatusBar,
SafeAreaView,
TextInput,
TouchableOpacity,
Text,
StyleSheet
} from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: 23
},
input: {
margin: 15,
height: 40,
borderColor: '#7a42f4',
borderWidth: 1
},
submitButton: {
backgroundColor: '#7a42f4',
padding: 10,
margin: 15,
height: 40,
},
submitButtonText:{
color: 'white'
}
});
export default class LoginView extends Component {
constructor(){
super()
this.state = {
isLoading: false
}
}
changeLoadingState = (currentLoadingState) => {
/* Create a loader screen and incorporate it here.
*/
this.setState({isLoading: currentLoadingState} , () => {
console.log("This is called when this.setState has resolved");
console.log(this.state.isLoading);
});
}
render() {
const con = new LoginController(this.changeLoadingState);
return (
<Fragment>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<View style = {styles.container}>
<TextInput style = {styles.input}
underlineColorAndroid = "transparent"
placeholder = "Email"
placeholderTextColor = "#9a73ef"
autoCapitalize = "none"
onChangeText = {con.handleEmail}/>
<TextInput style = {styles.input}
underlineColorAndroid = "transparent"
placeholder = "Password"
placeholderTextColor = "#9a73ef"
autoCapitalize = "none"
onChangeText = {con.handlePassword}/>
<TouchableOpacity
style = {styles.submitButton}
onPress = {
() => con.login()
}>
<Text style = {styles.submitButtonText}> Submit </Text>
</TouchableOpacity>
</View>
</SafeAreaView>
</Fragment>
);
}
}
LoginController.js
import React, { Component } from 'react';
import LoginNetworkManager from '../NetworkManager/LoginNetworkManager.js';
import Loader from '../../Utils/Loader.js';
export default class LoginController extends Component {
constructor(props) {
super(props);
this.state = {
email: null,
password: null
};
this.changeLoadingState = this.changeLoadingState.bind(this);
}
changeLoadingState = (currentLoadingState) => {
this.props.changeLoadingState(currentLoadingState);
}
handleEmail = (text) => {
this.setState({email: text});
}
handlePassword = (text) => {
this.setState({password: text});
}
login = () => {
this.changeLoadingState(this.validate());
if (this.validate() == true) {
// Here in we will call the API
} else {
console.log(" It's false ");
// Do nothing
}
}
validate = () => {
var reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
var isValid = reg.test(this.email);
if (isValid) {
isValid = (this.password.trim().length > 0);
}
console.log(" Tis is Valid " + isValid);
return isValid
}
}
The error when tapped the login button is
_this.props.changeLoadingState is not a function
handleException @ ExceptionsManager.js:86
handleError @ setUpErrorHandling.js:23
reportFatalError @ error-guard.js:42
__guard @ MessageQueue.js:345
callFunctionReturnFlushedQueue @ MessageQueue.js:105
(anonymous) @ debuggerWorker.js:80
Upvotes: 1
Views: 3703
Reputation: 584
Thanks to Dupocas's reply I understood when to utilise Components and when not to.
In my case, LoginController shouldn't be a component, because there is nothing to render in it's logic. It is purely a helper class.
The result code is now as follows
import React, { Component, Fragment} from 'react';
import LoginController from '../Controller/LoginController.js';
import Loader from '../../Utils/Loader';
import {
View,
StatusBar,
SafeAreaView,
TextInput,
TouchableOpacity,
Text,
StyleSheet
} from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: 23
},
input: {
margin: 15,
height: 40,
borderColor: '#7a42f4',
borderWidth: 1
},
submitButton: {
backgroundColor: '#7a42f4',
padding: 10,
margin: 15,
height: 40,
},
submitButtonText:{
color: 'white'
}
});
export default class LoginView extends Component {
constructor(){
super()
this.state = {
isLoading: false
}
con = new LoginController();
}
changeLoadingState = (currentLoadingState,completionBlock) => {
this.setState({isLoading: currentLoadingState} , completionBlock);
}
render() {
return (
<Fragment>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<View style = {styles.container}>
<Loader
loading={this.state.isLoading} />
<TextInput style = {styles.input}
underlineColorAndroid = "transparent"
placeholder = "Email"
placeholderTextColor = "#9a73ef"
autoCapitalize = "none"
onChangeText = {con.handleEmail}/>
<TextInput style = {styles.input}
underlineColorAndroid = "transparent"
placeholder = "Password"
placeholderTextColor = "#9a73ef"
autoCapitalize = "none"
onChangeText = {con.handlePassword}/>
<TouchableOpacity
style = {styles.submitButton}
onPress = {
() => con.login(this.changeLoadingState)
}>
<Text style = {styles.submitButtonText}> Submit </Text>
</TouchableOpacity>
</View>
</SafeAreaView>
</Fragment>
);
}
}
LoginController is
import LoginNetworkManager from '../NetworkManager/LoginNetworkManager.js';
export default class LoginController {
email = null;
password = null;
changeLoadingState = (currentLoadingState,viewCallback,completionBlock) => {
viewCallback(currentLoadingState,completionBlock);
}
handleEmail = (text) => {
this.email = text
}
handlePassword = (text) => {
this.password = text
}
login = (viewCallback) => {
this.changeLoadingState(this.validate(),viewCallback);
if (this.validate() == true) {
let params = { email : this.email, password : this.password};
LoginNetworkManager.loginAPI(params, (response,error) => {
this.changeLoadingState(false,viewCallback,() => {
if (error){
}else{
}
});
});
}
}
validate = () => {
var reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
var isValid = reg.test(this.email);
console.log(" this.email " + this.email);
console.log(" this.password " + this.password);
if (isValid) {
isValid = (this.password.trim().length > 0);
console.log(" password validation ----> " + isValid);
}
return isValid
}
}
Although, Dupocas's mentioned out about HOC and RenderProps and Hooks, I believe, that if I don't need a Component, I should try it in the non-component way, although it was insightful and might help me in future complex scenarios.
Upvotes: 0
Reputation: 21357
The problem here is that LoginController
isn't a Component
, if you want LoginController
to be only a helper class, than you should remove state
and props
from it:
export default class LoginController {
changeLoadingState = (currentLoadingState) => {
}
handleEmail = (text) => {
}
handlePassword = (text) => {
}
login = () => {
this.changeLoadingState(this.validate());
if (this.validate() == true) {
// Here in we will call the API
} else {
console.log(" It's false ");
// Do nothing
}
}
validate = () => {
var reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
var isValid = reg.test(this.email);
if (isValid) {
isValid = (this.password.trim().length > 0);
}
console.log(" Tis is Valid " + isValid);
return isValid
}
}
But if your goal is to abstract stateful logic, then you're doing it wrong. When you extend React.Component
from a class you are explicitly telling React that this class is a Component
therefore it should return JSX
(render()) and should be initialized as a Component: <LoginController />
, to abstract stateful logic you actually have a lot of really cool alternatives:
High Order Components (HOC)
This seens to be your use case, since you want a inject some props into LoginView
, so you could abstract the logic to a HOC:
import React, { Component } from 'react';
import LoginNetworkManager from '../NetworkManager/LoginNetworkManager.js';
import Loader from '../../Utils/Loader.js';
export default withLogin = (ChildComponent) => {
return class LoginController extends Component {
constructor(props) {
super(props);
this.state = {
email: null,
password: null
};
this.changeLoadingState = this.changeLoadingState.bind(this);
}
/*
Your logic
*/
render(){
return <ChildComponent {...this.state} />
}
}
}
Now inside LoginView
you export like this: export default withLogin(LoginView)
and LoginController
's state will be serialized inside LoginView
's props: this.props.email
and this.props.password
And of course, everything that can be done with an HOC can also be done using renderProps
and hooks
.
Upvotes: 1
Reputation: 12430
Your not passing the function as a prop to your LoginController component.
Upvotes: 0