My Login.js file was getting larger and larger so I decided to create a reusable component in a separate file, LoginForm.js. However, after I extracted the previous code from Login.js into LoginForm.js one of my props is getting undefined and I have no idea why.
The prop who's is getting undefined: this.props.token
I've imported all the modules and compared the code, and everything seems to be correct.
File 1
In Login.js I have this:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createToken } from '../../actions/tokenActions'
import LoginForm from './LoginForm'
// Styles
import Typography from 'material-ui/Typography'
import Card, { CardActions, CardContent } from 'material-ui/Card'
import Button from 'material-ui/Button'
import { CircularProgress } from 'material-ui/Progress'
import injectSheet from 'react-jss'
class Login extends Component {
constructor(props) {
super(props)
this.state = {}
}
onFormSubmit(e) {
e.preventDefault()
this.props.createToken(this.state)
}
render() {
const progressBar = this.props.token.pending
? <CircularProgress className={this.props.progress} />
: ''
return (
<div className="box" style={{ margin: '100px auto', width: '300px' }}>
{progressBar}
<form
onSubmit={this.onFormSubmit.bind(this)}
style={{ display: this.props.token.pendling ? 'none' : 'block' }}
>
<LoginForm />
</form>
</div>
)
}
}
const mapState = state => {
return {
token: state.token
}
}
const mapDispatch = (dispatch, props) => ({
createToken: data => dispatch(createToken(data))
})
export default connect(mapState, mapDispatch)(Login)
File 2
And in my other file, LoginForm.js I have this:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createToken } from '../../actions/tokenActions'
//Styles
import Card, { CardActions, CardContent } from 'material-ui/Card'
import Button from 'material-ui/Button'
import Typography from 'material-ui/Typography'
export default class LoginForm extends Component {
constructor(props) {
super(props)
this.state = {}
}
onFormSubmit(e) {
e.preventDefault()
this.props.createToken(this.state)
}
render() {
console.log(this.props.token)
return (
<Card>
<CardContent>
<Typography
style={{
fontSize: '40px',
fontWeight: '300',
color: '#636363',
marginBottom: '20px'
}}
type="subheading"
gutterBottom
align="center"
>
Logga In
</Typography>
<input
type="text"
placeholder="Email"
onChange={e => this.setState({ email: e.target.value })}
/>
<input
type="password"
placeholder="Lösenord"
onChange={e => this.setState({ password: e.target.value })}
/>
<Button
raised
color="primary"
onClick={this.onFormSubmit.bind(this)}
className="text-center"
>
Skicka
</Button>
{this.props.token.rejected
? 'Felaktigt användarnamn eller lösenord'
: ''}
</CardContent>
</Card>
)
}
}
I bet it's a really basic question, but I have no other to ask than here. I am really grateful for all the help!
<LoginForm /> doesnt provide any props.
this.props wouldn't be undefined if you provided it like this: <LoginForm token={this.props.token} /> props doesnt get passed on if you dont explicit write it, you can write them one by one but you can also type like this: <LoginForm {...this.props} />
Related
As I'm new to both react and typescript I've read several articles about how to get the "match" property from react-router working using typescript and I haven't been able to successfully do it. I have no parameters I am passing, I just want to be able to use the match.url of react-router.
So the examples I am seeing is that the field in the login.tsx file should use {match.url}. I tried the react 4 router demo here: https://codesandbox.io/s/nn8x24vm60?from-embed where they used {${match.url}/register} but that didn't work either. Then I saw some posts where people said you had to declare an interface but it was split between RouteComponent and Route and most were dealing with parameters. Basically all I want it to do is when I click the link to switch to the register route(it's in register.tsx - not shown here as it's a simple file with just a header on it).
Currently its throwing the following eror:
"Error TS2739 (TS) Type '{}' is missing the following properties from type
'Readonly<MyProps & RouteComponentProps<{}, StaticContext, any>>': register, history, location, match"
Any help as to what I'm doing wrong would be appreciated.
app.tsx file:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Redirect, Link, Route, Switch } from 'react-router-dom';
import { Home } from './home';
import { Register } from './register';
import { NavBar } from './navbar';
export class App extends React.Component<{}, {}> {
render() {
return (
<Router>
<div>
<NavBar/>
<Switch>
<Route path="/" component={Home} />
<Route path="/register" component={Register} />
</Switch>
</div>
</Router>
);
}
}
ReactDOM.render(, document.getElementById('root'));
home.tsx file:
import React from 'react';
import { Login } from './login';
import { Jumbotron } from 'react-bootstrap';
const jumboStyle = {
background: 'lightgray',
height: '20%',
width: '40%',
margin: 'auto'
};
export class Home extends React.Component<{}, {}> {
render() {
return (
<div>
< Jumbotron style={jumboStyle}>
<h1>Welcome to the new League!</h1>
<h4>Please log in with your username and password to continue</h4>
<Login />
<br />
<br />
</Jumbotron>
</div>
);
}
}
login.tsx file:
import React from 'react';
import { Link, RouteComponentProps } from "react-router-dom";
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import { Alert } from 'react-bootstrap';
import { Register } from './register';
interface IState {
[key: string]: any; // or the type of your input
}
interface MyProps {
register: Register
}
const styles = {
background: 'lightblue'
};
export class Login extends React.Component<MyProps & RouteComponentProps, IState> {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
authorized: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]:value
});
}
handleSubmit(event) {
//we are goingto submit the form to the database
event.prevent.default();
}
render() {
return (
<div>
<form noValidate autoComplete="off" onSubmit={this.handleSubmit} style={{ textAlign: 'center' }}>
<TextField
id="username"
name="username"
label="UserName"
helperText="Enter your Username"
value={this.state.username}
onChange={this.handleChange}
required={true}
style={styles}
/>
<br />
<TextField
id="password"
name="password"
type="password"
helperText="Enter your password"
label="Password"
onChange={this.handleChange}
required={true}
style={styles}
/>
<br />
<br />
<br/>
<Button
type="submit"
value="Submit"
variant="contained"
color="primary"
>Submit</Button>
<br />
<br/>
<Alert variant="info">
<Alert.Heading>Don't Have An Account Setup?</Alert.Heading>
<div>
<Link to={`${this.props.match.url}/register`}>Register Here</Link>
</div>
</Alert>
</form>
</div>
)
}
}
By default the react-router properties (such as match) are only automatically injected into the component rendered by a route (Home in this case). You can add them to your component using the withRouter wrapper. You then have to use the wrapped component instead of the internal one.
import {withRouter} from 'react-router';
...
class LoginInternal extends React.Component<MyProps & RouteComponentProps, IState> {
...
export const Login = withRouter(LoginInternal);
You need to pass props from Home component to Login component.
<Login match={this.props.match}/>
It should work.
I defined the prop _navigateToRegister, but when I press on the button nothing happens.
And also I have this mistake warning: Warning: Failed prop type the prop "onPress" is marked as required in "Button", but its value is "undefined"
Where is the mistake?
app.js
import React, { Component } from 'react';
import {
AppRegistry
} from 'react-native';
import {
StackNavigator,
} from 'react-navigation';
import { createStackNavigator } from 'react-navigation';
import Login from './src/screens/Login';
import Secured from './src/screens/Secured';
import Register from './src/screens/Register';
const LifeStorm = createStackNavigator({
Login: { screen: Login },
Register: { screen: Register },
Secured: { screen: Secured },
});
export default class ReactNativeStormpath extends Component {
state = {
isLoggedIn: false
}
render() {
if (this.state.isLoggedIn)
return <Secured
onLogoutPress={() => this.setState({isLoggedIn: false})}
/>;
else
return <Login
onLoginPress={() => this.setState({isLoggedIn: true})}
/>;
}
}
AppRegistry.registerComponent("App", () => App);
login.js
import React, { Component } from "react";
import { ScrollView, Text, TextInput, View, Button } from "react-native";
export default class Login extends Component {
navigateToRegister = () => {
this.props.navigation.navigate("Register");
};
render() {
return (
<ScrollView style={{ padding: 20 }}>
<Text style={{ fontSize: 27 }}>Login</Text>
<TextInput placeholder="Username" />
<TextInput placeholder="Password" />
<View style={{ margin: 7 }} />
<Button onPress={this.props.onLoginPress} title="Submit" />
<Button onPress={this.props._navigateToRegister} title = "Register" />
</ScrollView>
);
}
}
module.exports = Login;
register.js
import React, { Component } from 'react';
import {
ScrollView,
Text,
TextInput,
View,
Button
} from 'react-native';
export default class Register extends Component {
render() {
return (
<ScrollView style={{padding: 20}}>
<Text
style={{fontSize: 27}}>
Register
</Text>
<TextInput placeholder='E-Mail' />
<TextInput placeholder='Username' />
<TextInput placeholder='Password' />
<TextInput placeholder='Confirm Password' />
<View style={{margin:7}} />
<Button
onPress={() => navigate("Login")}
title="Submit"
/>
</ScrollView>
)
}
}
P.S I am new in React Native.
Seems to me that you're not actually sending the _navigateToRegister prop to the component.
Which means that event if you log this.props._navigateToRegister to the console the result should be undefined. And because you're sending undefined value to onPress you get that prop type error.
If you want to reference the navigateToRegister method of your Login component you can do it like this:
<Button onPress={this.navigateToRegister} title = "Register" />
I got stuck with showing loading icon on initial page loading. The code looks fine for me but some how it doesn't shows the loading icon. I am using material ui RefreshIndicator for loading icon. I am using material-ui V^0.18.7.
Here is my initial App.js code
import React, { Component } from 'react';
import HeaderTemplate from './Layout/Components/Header';
import FooterTemplate from './Layout/Components/Footer';
import RefreshIndicator from 'material-ui/RefreshIndicator';
import RaisedButton from 'material-ui/RaisedButton';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
class App extends Component {
constructor(props){
super(props);
this.state = {
showLoading: true
}
}
componentDidMount(){
this.setState({
showLoading: false
})
}
renderLoading(){
<RefreshIndicator
size={40}
left={10}
top={0}
status="loading"
style={{display: 'inline-block', position: 'relative'}}
/>
}
render() {
const muiTheme = imports.getMuiTheme({
});
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div>
<HeaderTemplate logo="Postme" />
<div id="demo-settings" className="">
</div>
<div className="container-fluid bodyfluid">
{this.state.showLoading ? this.renderLoading(): ''}
{this.props.children}
</div>
<FooterTemplate />
</div>
</MuiThemeProvider>
);
}
}
export default App;
Your method renderLoading() doesn't return anything. It just creates the JSX-Element into "nothing".
import React, { Component } from 'react';
import HeaderTemplate from './Layout/Components/Header';
import FooterTemplate from './Layout/Components/Footer';
import RefreshIndicator from 'material-ui/RefreshIndicator';
import RaisedButton from 'material-ui/RaisedButton';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
class App extends Component {
constructor(props){
super(props);
this.state = {
showLoading: true
}
}
componentDidMount(){
this.setState({
showLoading: false
})
}
renderLoading(){
return (<div style={{position: 'relative'}}>
<RefreshIndicator
size={40}
left={10}
top={0}
status="loading"
style={{display: 'inline-block', position: 'relative'}}
/>
</div>);
}
render() {
const muiTheme = imports.getMuiTheme({
});
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div>
<HeaderTemplate logo="Postme" />
<div id="demo-settings" className="">
</div>
<div className="container-fluid bodyfluid">
{this.state.showLoading ? this.renderLoading(): ''}
{this.props.children}
</div>
<FooterTemplate />
</div>
</MuiThemeProvider>
);
}
}
export default App;
Now it returns the JSX element your created, and it should be rendered as you like.
I am trying to implement a Horizon Server (Express.js+RethinkDB), and a React.js Horizon Client which is (React + Material-UI). This is two separated application.
I have found a really good example on how to setup custom logins in Horizon: https://github.com/tailsu/horizon-custom-login so I have started to configure that as a backend.
I am trying to implement Horizon at the moment, Horizon needs a session_token and not sure where to place the connection string for Horizon, I have a login form and I receive back the session token and I am saving that into localStorage but when the page loads localStorage seems to be empty.
If I reload the page the token works and everything seems to be okay.
Does anyone know how to handle this properly?
This is my current code:
Dashboard:
import React from 'react';
import {Link, withRouter} from 'react-router-dom';
import axios from 'axios';
import {backendUrl, backendApiKey, apiRequestRate} from '../../config';
import {Redirect} from 'react-router';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import IconButton from 'material-ui/IconButton';
import Typography from 'material-ui/Typography';
import AccountCircle from 'material-ui-icons/AccountCircle';
import Menu, {MenuItem} from 'material-ui/Menu';
import PropTypes from 'prop-types';
import Tabious from './tabious';
import {dashTheme} from '../../themes/dashTheme';
import Horizon from '../../horizon-container';
import LoginForm from '../login';
const _horizon = Horizon.get();
class Dashboard extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
token: localStorage.getItem('session_token')
}
}
static contextTypes = {
router: PropTypes.object
}
state = {
anchorEl: null
};
componentDidMount(){
_horizon.connect();
this.timer = setInterval(() => {
if(_horizon.hasAuthToken() == false){
console.log('why???');
}
}, 1000);
}
componentWillUnmount(){
clearInterval(this.timer);
}
handleMenu = event => {
this.setState({anchorEl: event.currentTarget});
};
handleRequestClose = () => {
this.setState({anchorEl: null});
};
handleLogOut = (props) => {
localStorage.removeItem('session_token');
Horizon.clearAuthTokens();
this.context.router.history.push("/login");
};
handleMyAcc = (event : any, value : any) => {
this.context.router.history.push(value);
};
render() {
const {anchorEl} = this.state;
const open = Boolean(anchorEl);
return (
<div>
<AppBar style={dashTheme.bar}>
<Toolbar>
<Link to="/">
<IconButton color="contrast" aria-label="Menu">
<img src="/logoico.png" alt=""/>
</IconButton>
</Link>
<Typography type="title" style={dashTheme.typo}>
Chalton PMS
</Typography>
<Tabious/>
<div>
<IconButton aria-owns={open
? 'menu-appbar'
: null} aria-haspopup="true" onClick={this.handleMenu} color="contrast">
<AccountCircle/>
</IconButton>
<Menu id="menu-appbar" anchorEl={anchorEl} anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}} transformOrigin={{
vertical: 'top',
horizontal: 'right'
}} open={open}
onClose={this.handleRequestClose}
>
<MenuItem onClick={this.handleLogOut}>Log Out</MenuItem>
<MenuItem onTouchTap={() => {
this.context.router.history.push('/myaccount')
}}>My account</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
{this.props.children}
</div>
);
}
}
export default withRouter(Dashboard);
Horizon Container:
import Horizon from '#horizon/client';
import {backendUrl} from './config';
console.log(localStorage.getItem('session_token'));
const _horizon = Horizon({host: backendUrl, authType: {token: localStorage.getItem('session_token'), storeLocally: true}});
export default {
get: () => _horizon,
clearAuthTokens: () => Horizon.clearAuthTokens()
}
Login:
import React from 'react';
import {Redirect} from 'react-router';
import axios from 'axios';
import {withRouter} from 'react-router-dom';
import {MuiThemeProvider} from 'material-ui/styles';
import Paper from 'material-ui/Paper';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import IconButton from 'material-ui/IconButton';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import PropTypes from 'prop-types';
import {ValidatorForm, TextValidator} from 'react-material-ui-form-validator';
import {loginUrl} from '../config';
import Dashboard from './dashboard/dashboard';
import {loginTheme} from '../themes/loginTheme';
class LoginForm extends React.Component {
constructor(props,context) {
super(props,context);
this.state = {
login: {
username: '',
pwd: ''
},
token: localStorage.getItem('session_token')
};
this.handleSubmit = this.handleSubmit.bind(this);
}
static contextTypes = {
router: PropTypes.object
}
handleChange(property, event) {
const login = {
...this.state.login
};
login[property] = event.target.value;
this.setState({login});
}
handleSubmit(event) {
let payload = 'username=' + this.state.login.username + '&password=' + this.state.login.pwd;
let headers = {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
};
let self = this;
axios.post(loginUrl + '/login', payload, headers).then(function(response) {
localStorage.setItem('session_token', response.data.token);
localStorage.setItem('session_user', self.state.login.username);
self.context.router.history.push("/");
}).catch(function(error) {
self.setState({'ErrorMSG': 'These authentication details are invalid.'});
});
event.preventDefault();
}
render() {
if(this.state.token){
<Redirect to="/" />
}
return(
<div>
<MuiThemeProvider theme={loginTheme}>
<div>
<Paper style={loginTheme.paper}>
<AppBar position="static" style={loginTheme.bar}>
<Toolbar>
<IconButton color="contrast" aria-label="Menu">
<img src="/logoico.png" alt=""/>
</IconButton>
<Typography type="title" style={loginTheme.typo}>
Chalton PMS
</Typography>
</Toolbar>
</AppBar>
<ValidatorForm ref="form" onSubmit={this.handleSubmit} onError={errors => console.log(errors)}>
<TextValidator label="Username" name="username" style={loginTheme.tf} value={this.state.login.username} onChange={this.handleChange.bind(this, 'username')} validators={['required']} errorMessages={['This field is required.', 'Username is not valid.']}/>
<TextValidator label="Password" name="password" type="password" style={loginTheme.tf} value={this.state.login.pwd} onChange={this.handleChange.bind(this, 'pwd')} validators={['required']} errorMessages={['This field is required.']}/>
<p style={loginTheme.errorMSG}>{this.state.ErrorMSG}</p>
<Button raised type="submit" style={loginTheme.loginBTN} color="primary">
Login
</Button>
</ValidatorForm>
</Paper>
</div>
</MuiThemeProvider>
</div>
);
}
}
export default withRouter(LoginForm);
If I leave the container behind it seems to be working.
Code in Dashboard:
componentDidMount(){
const _horizon = new Horizon({host: backendUrl, authType: {token: localStorage.getItem('session_token'), storeLocally: true}});
_horizon.connect();
console.log(_horizon.status());
this.timer = setInterval(() => {
if(_horizon.hasAuthToken() == false){
this.handleLogOut
}
}, 1000);
}
I don't understand how can I access to form props. I used formValueSelector as suggested the documentation, but doesn't work. Where is my mistake? I'm using the latest version of redux-form.
LoginForm.js
'use strict';
import React from 'react';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { TextInput, View, TouchableHighlight, Text } from 'react-native';
import { connect } from 'react-redux';
class LoginForm extends React.Component {
handeSubmit(email, password){ alert(`email: ${email} and password: ${password}`)};
render() {
return (
<View>
<Field
name="email"
component={TextInput}
placeholder="Email"
/>
<Field
name="password"
component={TextInput}
placeholder="Password"
secureTextEntry={true}
/>
<TouchableHighlight onPress={() => this.handeSubmit(this.props.email, this.props.password)}>
<Text>Submit</Text>
</TouchableHighlight>
</View>
);
}
}
LoginForm = reduxForm({
form: 'loginForm'
})(LoginForm);
const selector = formValueSelector('loginForm');
function mapStateToProps(state){
return {
email: selector(state, 'email'),
password: selector(state, 'password'),
}
}
LoginForm = connect(mapStateToProps)(LoginForm);
export default LoginForm;
LoginPage.js
'use strict';
import React from 'react';
import {View} from 'react-native';
import Container from '../../components/Container';
import LoginForm from '../../components/forms/LoginForm';
class LoginPage extends React.Component {
render() {
return (
<Container>
<LoginForm/>
</Container>
);
}
}
export default LoginPage;
Result:
I use alert(this.props) onPress button and this is the output. There aren't email and password.
and this is the output for
<TouchableHighlight onPress={() => alert(JSON.stringify(test))}>
<Text>Submit</Text>
</TouchableHighlight>
function mapStateToProps(state){
return {
test: state.form.loginForm
}
}
I found the solution. TextInput doesn't work with redux-form as it is. It need a component than pass the redux-form props like this:
TextField.js
class TextField extends React.Component {
render(){
const { input: { value, onChange } } = this.props; <----add this!!!
return (
<TextInput
style={[styles.textInput]}
onChangeText={(value) => onChange(value)} <----add this!!!
add this!!!---> value={value} underlineColorAndroid="transparent" selectTextOnFocus={true} {...this.props}
/>
);
}
}
Instead of import TextInput inside the forms, import the custom TextField.