I'm not sure what the deal is probably something stupid but when my handle email and handle password handlers are hit, I get null values for text that I entered on the form.
LoginContainer
import React, { Component } from 'react'
import * as AsyncActions from '../actions/User/UserAsyncActions'
import Login from '../components/Login/Login'
class LoginContainer extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: ''
}
this.emailIsValid = this.emailIsValid.bind(this)
this.handleEmailInput = this.handleEmailInput.bind(this)
this.handlePasswordInput = this.handlePasswordInput.bind(this)
this.handleLoginPressed = this.handleLoginPressed.bind(this)
}
handlePasswordInput(e, password) {
e.persist()
this.setState(password )
}
handleEmailInput(e, email) {
e.persist()
this.setState(email)
}
handleLoginPressed(e) {
e.persist()
// e.preventDefault()
if (this.emailIsValid(this.state.email) &&
this.passwordIsValid(this.state.password)) {
this.props.login(this.state.email, this.state.password)
}
}
emailIsValid(e, email) {
e.persist()
if (!email) {
return false
}
return true
}
passwordIsValid(e, password) {
e.persist()
if (!password) {
return false
}
return true
}
render(){
return( <Login
handleEmailInput={this.handleEmailInput}
handlePasswordInput={this.handlePasswordInput}
login={this.handleLoginPressed}
/> )
}
}
const mapStateToProps = state => {
return {
requesting: state.user.requesting,
loggedIn: state.user.loggedIn,
token: state.user.token,
session: state.user.session
}
}
export const mapDispatchToProps = {
login: AsyncActions.login
}
export { Login }
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
Login
class Login extends Component {
render(){
return (
<div>
<LoginForm
handleEmailInput={this.props.handleEmailInput}
handlePasswordInput={this.props.handlePasswordInput}
login={this.props.login}
/>
</div>
)
}
}
export default Login
LoginForm
import React, { Component } from 'react'
import { Button, FormControl, FormGroup, ControlLabel, PageHeader } from 'react-bootstrap'
class LoginForm extends Component {
render(){
return (
<div className='ft-login-form'>
<PageHeader className='ft-header'>Login</PageHeader>
<form onSubmit={this.props.login}>
<FormGroup controlId="formBasicText" >
<ControlLabel>Email</ControlLabel>
<FormControl
bsSize="small"
className="ft-username"
componentClass="input"
onChange={this.props.handleEmailInput}
placeholder="Enter mail"
style={{ width: 300}}
type="text"
// value={this.state.email}
/>
<ControlLabel>Password</ControlLabel>
<FormControl
bsSize="small"
className="ft-password"
componentClass="input"
onChange={this.props.handlePasswordInput}
placeholder="Enter Password"
style={{ width: 300}}
type="text"
// value={this.state.password}
/>
</FormGroup>
<Button
className='ft-login-button'
type='submit'>Login</Button>
</form>
</div>)
}
}
export default LoginForm
It looks like you were on the right path with the value={this.state.password}. But since your state is in the parent component, you have to pass the state down and the value becomes value={this.props.value}. The event handlers usually look something like this:
handlePasswordInput(e, password) {
e.persist()
this.setState({ password: e.target.value })
}
They could be different due to the FormControl component but it's worth changing them to see if that's your problem. Also, onChange handlers implicitly pass in e and you have to use a arrow function expression to explicitly pass in anything else.
Edit: If they were able to do something like you mentioned in the comment, they probably did something like this:
handlePasswordInput(e, password) {
e.persist()
const password = e.target.value
this.setState({ password })
}
In es6, { password } is the same as { password: password}
Related
When I try to type in the boxes on the webpage it doesn't register that I am typing anything. I am guessing it has something to do with the handleChange or onChange, but I could use some help here. I am still pretty new to React and trying to figure it out. What am I missing here?
import React, {Component} from 'react';
import { Form } from 'semantic-ui-react';
class Assessments extends Component {
state = {assessment_name: '', assessment_type: ''}
componentDidMount() {
if (this.props.id) {
const { assessment_name, assessment_type } = this.props
this.setState({ assessment_name, assessment_type })
}
}
handleChange = (a) => {
const { name, value } = a.target
this.setState({ [name]: value })
}
handleSubmit = (a) => {
a.preventDefault()
if (this.props.id) {
const { id, history } = this.props
this.props.updateName(id, this.state, history)
this.props.toggleUpdate()
}
this.props.close()
this.setState({ assessment_name: '', assessment_type: ''})
}
close = () => this.setState({ open: false })
render() {
const { assessment_name, assessment_type } = this.state
return(
<Form onSubmit={this.handleSubmit}>
<Form.Input
name=''
value={assessment_name}
onChange={this.handleChange}
label='Assessment Name'
required
/>
<Form.Input
name='AssessmentType'
value={assessment_type}
onChange={this.handleChange}
label='Assessment Type'
required
/>
<Form.Button>Submit</Form.Button>
</Form>
)
}
}
export default Assessments;
You're not passing the right names to the Form.Input components which the handleChange function uses to update the state. They have to be 'assessment_name' and 'assessment_type' respectively to make sure the state gets updated on input change events and the new values get reflected on the fields.
<>
<Form.Input
name="assessment_name"
value={assessment_name}
onChange={this.handleChange}
label="Assessment Name"
required
/>
<Form.Input
name="assessment_type"
value={assessment_type}
onChange={this.handleChange}
label="Assessment Type"
required
/>
</>
i've been trying since days to redirect my user after login to the home creating a callback function in my App.js and sending it as props to the login class component throught a loginregisterpage class component, but this doesn't work, can someone have a look on it and tell me what i;m missing out?
Thank you my code look like this
App.js
import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import { HomePage } from './Pages/HomePage/HomePage'
import { LoginRegisterPage } from './Pages/LoginRegisterPage/LoginRegisterPage'
import 'bootstrap/dist/css/bootstrap.min.css'
export class App extends React.Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
}
this.handleSuccess = this.handleSuccess.bind(this);
}
handleSuccess = (data) => {
this.props.history.push("/")
}
render() {
return (
<Router>
<Switch>
<Route exact path="/">
<HomePage />
</Route>
<Route exact path="/login-register">
<LoginRegisterPage onLoginSuccess={this.handleSuccess} />
</Switch>
</Router>
)
}
}
LoginRegisterPage class component
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
accessToken: '',
authenticated: ''
};
this.handleChangeUsername = this.handleChangeUsername.bind(this);
this.handleChangePassword = this.handleChangePassword.bind(this);
}
handleChangeUsername(event) {
this.setState({
username: event.target.value
})
}
handleChangePassword(event) {
this.setState({
password: event.target.value
})
}
handleClick(event) {
var apiBaseUrl = "https://myapi.com/auth/"
const payload = {
method: "POST",
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify({
'username': this.state.username,
'password': this.state.password
})
};
const { username, password } = this.state;
if (username && password) {
fetch(apiBaseUrl + 'login', payload)
.then((response) => {
if (response.status === 200) {
alert("Logged In! You'll be redirected on Home")
return response.json()
} else {
return alert("wrong pass")
}
}).then((data) => {
this.setState({
accessToken: data.accestToken,
authenticated: data.authenticated
});
localStorage.setItem('accessToken', data.accessToken);
if (data.authenticated === true) {
console.log(this.props)
this.props.onLoginSuccess(data)
}
})
.catch((err) => console.log(err));
} else {
alert("Cannot be Empty")
}
}
render() {
return (
<div>
<div className="form">
<div>
<div className="form-input">
<div >
<div className="userData">
<span>
<img
src={UserIcon}
/>
</span>
<input
autocomplete="off"
type="text"
name="username"
placeholder="Username"
value={this.state.username}
onChange={this.handleChangeUsername}
/>
</div>
<div className="userData">
<span>
<img
src={PasswordIcon}
/>
</span>
<input
autocomplete="off"
type="password"
name="password"
placeholder="Password"
value={this.state.password}
onChange={this.handleChangePassword}
/>
<p style={(this.state.username && this.state.password) ? { display: 'none' } : { display: 'block' }}> Must fill all the form!</p>
</div>
</div>
</div>
</div>
</div>
<div className="form-footer">
<img
src={Btn}
onClick={(event) => this.handleClick(event)}
/>
</div>
</div>
);
}
}
LoginPage class component
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
accessToken: '',
authenticated: ''
};
this.handleChangeUsername = this.handleChangeUsername.bind(this);
this.handleChangePassword = this.handleChangePassword.bind(this);
}
handleChangeUsername(event) {
this.setState({
username: event.target.value
})
}
handleChangePassword(event) {
this.setState({
password: event.target.value
})
}
handleClick(event) {
var apiBaseUrl = "https://movies-app-siit.herokuapp.com/auth/"
const payload = {
method: "POST",
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify({
'username': this.state.username,
'password': this.state.password
})
};
const { username, password } = this.state;
if (username && password) {
fetch(apiBaseUrl + 'login', payload)
.then((response) => {
if (response.status === 200) {
alert("Logged In! You'll be redirected on Home")
return response.json()
} else {
return alert("wrong pass")
}
}).then((data) => {
this.setState({
accessToken: data.accestToken,
authenticated: data.authenticated
});
localStorage.setItem('accessToken', data.accessToken);
if (data.authenticated === true) {
console.log(this.props)
this.props.onLoginSuccess(data)
}
})
.catch((err) => console.log(err));
} else {
alert("Cannot be Empty")
}
}
render() {
return (
<div>
<div className="form">
<div>
<div className="form-input">
<div >
<div className="userData">
<span>
<img
src={UserIcon}
/>
</span>
<input
autocomplete="off"
type="text"
name="username"
placeholder="Username"
value={this.state.username}
onChange={this.handleChangeUsername}
/>
</div>
<div className="userData">
<span>
<img
src={PasswordIcon}
/>
</span>
<input
autocomplete="off"
type="password"
name="password"
placeholder="Password"
value={this.state.password}
onChange={this.handleChangePassword}
/>
<p style={(this.state.username && this.state.password) ? { display: 'none' } : { display: 'block' }}> Must fill all the form!</p>
</div>
</div>
</div>
</div>
</div>
<div className="form-footer">
<img
src={Btn}
onClick={(event) => this.handleClick(event)}
/>
</div>
</div>
);
}
}
If you're using React Router you can use the Redirect component:
import { Redirect } from 'react-router-dom';
export default function PrivateRoute () {
if (notLoggedIn()) {
return <Redirect to="/login"/>;
}
// return your component
}
But if you're not inside a render function (i.e. you're in a submit callback) or you want to rewrite browser history, use the useHistory hook (note: hooks work only in function components, not class components)
import { useHistory } from 'react-router-dom';
const history = useHistory();
// After your login action you can redirect with this command:
history.push('/otherRoute');
Issue
App is defined outside the Router component so it has no history prop function to call to do any navigation.
Solution
Have the LoginRegisterPage component navigate upon successful authentication. It will need to access the history object of the nearest Router context. Normally this is achieved by consuming passed route props from the Route component.
You can:
#1
Move LoginRegisterPage to be rendered by the component prop of the Route so it receives the route props and thus the history object as a prop.
<Route exact path="/login-register" component={LoginRegisterPage} />
LoginRegisterPage
class LoginPage extends React.Component {
constructor(props) {
...
}
...
handleClick(event) {
var apiBaseUrl = "https://myapi.com/auth/"
const payload = {...};
const { username, password } = this.state;
const { history } = this.props; // <-- destructure history from props
if (username && password) {
fetch(apiBaseUrl + 'login', payload)
.then((response) => {
...
}).then((data) => {
this.setState({
accessToken: data.accestToken,
authenticated: data.authenticated
});
localStorage.setItem('accessToken', data.accessToken);
if (data.authenticated === true) {
console.log(this.props)
this.props.history.push("/"); // <-- navigate!
}
})
.catch((err) => console.log(err));
} else {
alert("Cannot be Empty")
}
}
render() {
...
}
}
#2
Decorate your LoginRegisterPage with the withRouter Higher Order Component so the route props are injected as props.
import { withRouter } from 'react-router-dom;
...
const LoginPageWithRouter = withRouter(LoginPage);
Note
If you prefer to do a redirect then replace any history.push calls with history.replace. push is a normal navigation and pushes on a new path on the history state whereas replace replaces the current history entry in the stack. After the auth redirect you probably don't want users to back navigate back to your login page/route.
Edit
If you need the handleSuccess callback to manage some auth state in App then I think it best to let App manage the authentication state and the LoginPage to still handle navigation. In this case, go with the second solution above so it receives both the handleSuccess callback and the history object.
if (data.authenticated === true) {
this.props.onLoginSuccess(data); // <-- callback to parent to set state
this.props.history.replace("/"); // <-- imperative navigation
}
Define your handleSucess function in LoginRegisterPage instead of passing it as a prop and this should work.
I'm converting plain js files to typescript within a React application.
document is showing an error though when I use methods such as document.getElementById("login-username")
How can I refer to document methods in this typescript document?
import React, { useState, FormEvent, ChangeEvent } from 'react';
interface loginProps {
username: string,
setUsername(username: string): string,
setLoggedIn(loggedIn: boolean): boolean
}
export default function Login(props: loginProps) {
const { username, setUsername, setLoggedIn } = props;
const [err, setErr] = useState('');
function handleUsername(e: FormEvent<HTMLFormElement> ) {
const user = document.getElementById("login-username").value.trim();
const password = document.getElementById("login-password").value.trim();
if (user === '') { setErr('blank username'); } else { //ugly, will fix later
if (password === '') { setErr('blank password'); } else {
if (user.length < 2) { setErr('username must be at least 2 characters'); } else {
setUsername(user);
setLoggedIn(true);
}
}
}
e.preventDefault();
}
function setUserField(e: ChangeEvent<HTMLInputElement>) {
setUsername(e.target.value);
e.preventDefault();
}
return (
<form onSubmit={handleUsername} autoComplete="yes" style={{ padding: "10px" }}>
<span style={{ fontWeight: 'bold' }}>Login</span>
<br /><br />
<label htmlFor="login-username">Username:</label>
<input type="text"
padding="20px"
value={username}
onChange={setUserField}
name="login-username"
id="login-username" />
<label htmlFor="login-password">Password:</label>
...
So in ReactJS it's best to use refs
https://reactjs.org/docs/refs-and-the-dom.html
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Basically create a variable to access your element. Add the ref attribute on the specified element and assign it the variable. You will then have access to the element in the DOM as per React's standard.
EDIT:
Here's an example from the docs using hooks, useRef
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
I have a login component that stores the user information in the global state after a successful login. The login component is pretty straight forward. It contains a form with a handleSubmit event that calls an endpoint. Based on the result of that endpoint an action is taken. The login component looks like this.
import React, { Component } from 'react';
import { StateContext } from '../state';
import { login } from '../repositories/authenticationRepository';
class Login extends Component {
static contextType = StateContext;
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
message: '',
};
}
handleChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
}
handleSubmit = async (event) => {
event.preventDefault();
const [{}, dispatch] = this.context;
const { history } = this.props;
const { email, password } = this.state;
const isLoggedInResponse = await login({ email, password });
if (isLoggedInResponse.data.type === 'error') {
this.setState({ message: isLoggedInResponse.data.message });
return;
}
dispatch({ type: 'storeUserInformation', userInformation: isLoggedInResponse.data.message });
history.push('/');
}
render() {
const { email, password, message } = this.state;
return (
<div className="login-wrapper">
<form onSubmit={this.handleSubmit}>
<label htmlFor="email">
Email:
<input autoComplete="off" name="email" type="text" value={email} onChange={this.handleChange} />
</label>
<label htmlFor="password">
Password:
<input autoComplete="off" id="password" name="password" type="password" value={password} onChange={this.handleChange} />
</label>
{message.length > 0 && <span className="text-danger error">{message}</span> }
<input className="btn btn-secondary" type="submit" value="Submit" />
</form>
</div>
);
}
}
export default Login;
When testing it myself I can see the user information being set in the ReactJS devtools. Of course I want to test this automatically using a unit test, so I wrote the following.
jest.mock('../../repositories/authenticationRepository');
import React from 'react';
import { mount } from 'enzyme';
import Login from '../../pages/Login';
import { StateProvider } from '../../state';
import { login } from '../../repositories/authenticationRepository';
import { act } from 'react-dom/test-utils';
import history from '../../sitehistory';
import { BrowserRouter as Router } from 'react-router-dom';
import { reducer } from '../../reducer';
it('Saves the user information in the store on a succesfull login', async () => {
login.mockReturnValue(({ data: { type: 'success', message: 'Message should be stored' }}));
let initialStateMock = {}
const wrapper = mount(
<StateProvider initialState={initialStateMock} reducer={reducer}>
<Router>
<Login history={history} />
</Router>
</StateProvider>
);
let emailEvent = { target: { name: 'email', value: 'test#example.com'} }
let passwordEvent = { target: { name: 'password', value: 'password'} }
wrapper.find('input').first().simulate('change', emailEvent);
wrapper.find('input').at(1).simulate('change', passwordEvent);
const submitEvent = { preventDefault: jest.fn() }
await act(async () => {
wrapper.find('form').first().simulate('submit', submitEvent);
});
act(() => {
wrapper.update();
});
console.log(initialStateMock); // expected { userInformation: 'Message should be stored' } but got {}
});
I expect the initialStatemock to have the value of { userInformation: 'Message should be stored' }. However it still has the initial value of {}. I tried wrapper.update() to force a refresh but to no avail. What am I overlooking?
it seems that i am unable to get the form to rerender on each keystroke. I must be doing something wrong I just cannot tell what it is. The GetStartedForm function never runs twice, and neither does the function that contains the Field input.
use of redux form
const GetStartedForm = (props) => {
const {
handleSubmit,
pristine
} = props;
return (
<Form onSubmit={handleSubmit}>
<Field
name="email"
component={FormField}
type="text"
size="xl"
placeholder="Email Address"
autoComplete="email"
validate={[required(), email()]}
/>
<Button
disabled={pristine}>Get Started</Button>
</Form>
);
}
export default reduxForm({ form: 'getstarted' })(GetStartedForm);
implementation
<GetStarted onSubmit={e => {
console.log(e);
}} />
input field
const TextField = props => {
const { input, meta, size } = props;
console.log(props);
const properties = meta.uncontrolled ? {
defaultValue: props.defaultValue
} : {
value: input.value
};
return (
<React.Fragment>
{props.label && (
<label htmlFor={input.name} className="form__label">
{props.label}
{props.required ? ' *' : null}
</label>
)}
<Input
type={props.type ? props.type : 'text'}
className={props.className}
name={input.name}
id={input.name}
size={props.size}
readOnly={props.readOnly}
onChange={input.onChange}
autoFocus={props.autoFocus}
autoComplete={props.autoComplete}
placeholder={props.placeholder}
{...properties}
/>
{meta.touched && meta.error ? (
<div className="form__field-error">{meta.error}</div>
) : null}
</React.Fragment>
);
};
when i pass in uncontrolled like this i am able to get the text to at least show up on my screen, but i seem to be unable to get the form function to run again, which means that my button is stuck at disabled (pristine)
<Field
name="email"
component={FormField}
type="text"
size="xl"
placeholder="Email Address"
autoComplete="email"
validate={[required(), email()]}
meta={{uncontrolled: true}}
/>
yes reducers are set up
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { fromJS } from 'immutable';
import { combineReducers } from 'redux-immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import {
LOGOUT_USER_REQUEST
} from 'constants'
import globalReducer from 'containers/App/reducer';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
/*
* routeReducer
*
* The reducer merges route location changes into our immutable state.
* The change is necessitated by moving to react-router-redux#5
*
*/
// Initial routing state
const routeInitialState = fromJS({
location: null,
});
/**
* Merge route into the global application state
*/
function routeReducer(state = routeInitialState, action) {
switch (action.type) {
/* istanbul ignore next */
case LOCATION_CHANGE:
return state.merge({
location: action.payload,
});
default:
return state;
}
}
/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers) {
const composite = combineReducers({
route: routeReducer,
global: globalReducer,
language: languageProviderReducer,
form: formReducer,
...injectedReducers,
});
return rootReducer;
function rootReducer(state_, action) {
let state = state_;
if (action.type === LOGOUT_USER_REQUEST) {
state = null;
}
return composite(state, action);
}
}
I just realized that this react boilerplate uses immutable as its base so i had to import redux-form/immutable everywhere i was using redux-form