I have components in my react single page web app that require a password to view. Upon clicking the 's, a password form component is rendered. I have the logic written to check if the password is correct. If it is correct, how do I then leave the password form component, and render the component that the link was originally headed to?
I've tried just toggling visibility but I think I'm mostly confused on how specifically to use React Router to render a component on the condition that the password was correct
Parent Component
handleClick = (e) => {
e.preventDefault();
this.setState({ isPasswordVisible: !this.state.isPasswordVisible });
}
render() {
return (
<div className="BigNames">
<Link onClick={this.handleClick} className="BigNames-link" to='/Adobe' style={{textDecoration:'none'}}>
<span className='Name'>Adobe Creative Cloud</span>
<span className='Text'>: App Banner</span> <i className="fas fa-lock"></i>
</Link>
Password Component
import React, { Component } from 'react';
import './Password.css';
import Adobe from './Big Work/Adobe';
export default class Password extends Component {
static defaultProps = {
password: 'pierpoint'
}
constructor(props) {
super(props)
this.state = {
visible: true,
value: ''
}
this.handleClick = this.handleClick.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(e) {
e.preventDefault();
this.setState({value: e.target.value});
}
handleSubmit(e) {
e.preventDefault();
if(this.state.value === this.props.password) {
alert('Correct!');
this.setState({visible: false});
return( <Adobe />)
} else (alert('Incorrect Password!'))
}
handleClick(event) {
event.preventDefault()
this.setState(prevState => ({
visible: !prevState.visible,
}))
}
render() {
if (!this.state.visible) {
return null
}
return (
<div className="pwd">
<div className="enter-pwd">
<button className='exit' onClick={this.handleClick}> ✕ </button>
<form onSubmit={this.handleSubmit}>
<input
className="sub-text"
type='password'
name='password'
placeholder='Enter password'
value={this.state.value}
onChange={this.handleChange}>
</input>
<button
className="sub-mit"
type='submit'>
submit
</button>
</form>
</div>
</div>
)
}
}
The password component does go away after a correct password is submitted, however the following conditional component doesn't render.s
Here is the codepen showing the full example : Hidden by password page
In my example, the hidden page is a component I called SecretPage and the form handling the password is called Password. The parent component is App.
Because I needed to know inside of App whether the password was correct or not, the first step was to make Password a controlled component.
function Password(props){
return (
<div>
<p>Maybe the secret is a potato ?</p>
<form onSubmit={props.onSubmit}>
<input type='password' value={props.password} onChange={props.onChange}/>
<input type='submit' value='submit'/>
</form>
</div>);
}
What that means is simply that onSubmit, onChange and the value of password input itself are all given as props, and are handled by App and not by Password itself.
Here is how Password is called inside the App function
<Password password={this.state.password} onChange={this.handleChange} onSubmit={this.handleSubmit} />
Whenever the form is submitted, the function handleSubmit from App is called and it looks like this:
handleSubmit(e){
e.preventDefault();
this.setState({
secretVisible : this.checkPassword(this.state.password),
});
}
Because secretVisible is a state of the App now, knowing which page it should display is really easy. It only needs to check the this.state.secretVisible.
render(){
const secretVisible = this.state.secretVisible;
let pageToDisplay;
if(secretVisible){
pageToDisplay = <SecretPage onTakeMeBackClicked={this.handleLogOff}/>;
}
else{
pageToDisplay = <Password password={this.state.password} onChange={this.handleChange} onSubmit={this.handleSubmit} />;
}
return (
<div>
{pageToDisplay}
</div>
);
}
}
There are several ways to handle it. You can F.E use protected route togheter with react-router or in a simple case you can:
class App extends Component {
state = {
isAuthenticated: false,
}
setIsAuthenticated = (bool) => {
this.setState({isAuthenticated: bool})
}
render(){
const { isAuthenticated } = this.state;
return(
if(isAuthenticated){
return <YourMainComponent />
)
return <AuthFormComponent setIsAuthenticated={this.setIsAuthenticated} />
}
}
This is just an example, but I hope it gives you a tip on how to handle it.
There's not a ton of code here to give a proper example, but in pseudocode you'll want to do something like:
<div>
{isPasswordVerified
? <ComponentYouWantToShow />
: <Password callbackProp={setIsPasswordVerified} />
}
</div>
The Password component needs a callback prop to send whether the verification was successful to the parent component. Then in the parent component you can conditionally render the appropriate component. No need to deal w/ Routing here.
Related
I'm working on a CV Generator and I don't know how to properly append the school and field of study values to a new div inside React.
Using the onSubmit function I'm able to get the values after filling them out and clicking save, but I can't figure out where to go from here.
Update
What I want to do is take the values from the input and create a new div above the form that displays those values. For example, I want the School value to show
School: University of Whatever
And the same goes for Field of Study.
Field of Study: Whatever
I know how to do this in vanilla JS but taking the values and appending them to the DOM but it doesn't seem to work that way in React.
class Education extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit = (e) => {
e.preventDefault();
const schoolForm = document.getElementById("school-form").value;
const studyForm = document.getElementById("study-form").value;
};
render() {
return (
<>
<h1 className="title">Education</h1>
<div id="content">
<form>
<label for="school">School</label>
<input
id="school-form"
className="form-row"
type="text"
name="school"
/>
<label for="study">Field of Study</label>
<input
id="study-form"
className="form-row"
type="text"
name="study"
/>
<button onClick={this.onSubmit} className="save">
Save
</button>
<button className="cancel">Cancel</button>
</form>
)}
</div>
</>
);
}
}
export default Education;
You should use state in order to save the values then show it when the user submits.
import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { scool: "", study: "", showOutput: false };
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit = (e) => {
e.preventDefault();
this.setState({
showOutput: true
});
};
setStudy = (value) => {
this.setState({
study: value
});
};
setSchool = (value) => {
this.setState({
school: value
});
};
render() {
return (
<>
<h1 className="title">Education</h1>
<div id="content">
{this.state.showOutput && (
<>
<div>{`school: ${this.state.school}`}</div>
<div>{`study: ${this.state.study}`}</div>
</>
)}
<form>
<label for="school">School</label>
<input
id="school-form"
className="form-row"
type="text"
name="school"
onChange={(e) => this.setSchool(e.target.value)}
/>
<label for="study">Field of Study</label>
<input
id="study-form"
className="form-row"
type="text"
name="study"
onChange={(e) => this.setStudy(e.target.value)}
/>
<button onClick={this.onSubmit} className="save">
Save
</button>
<button className="cancel">Cancel</button>
</form>
)
</div>
</>
);
}
}
export default App;
I have also added 2 functions to set state and a condition render based on showOutput.
You don't append things to the DOM in react like you do in vanilla. You want to conditionally render elements.
Make a new element to display the data, and render it only if you have the data. (Conditional rendering is done with && operator)
{this.state.schoolForm && this.state.studyform && <div>
<p>School: {this.state.schoolForm}</p>
<p>Field of Study: {this.state.studyForm}</p>
</div>}
The schoolForm and studyForm should be component state variables. If you only have them as variables in your onSubmit, the data will be lost after the function call ends. Your onSubmit function should only set the state, and then you access your state variables to use the data.
Do not use document.getElementById. You don't want to use the 'document' object with react (Almost never).
You can access the element's value directly using the event object which is automatically passed by onSubmit.
handleSubmit = (event) => {
event.preventDefault();
console.log(event.target.school.value)
console.log(event.target.study.value)
}
I'm trying to do achieve just a simple login form for an app but for some reason cannot get my ternary operator to work. I'm simply importing the login credentials using a json file and using a submit handler to compare the user input with the data using a ternary. On submit, nothing happens. See my code below:
import React from 'react'
import customData from '../db.json'
import DocumentPage from './DocumentPage'
class MemberPortal extends React.Component {
state = {
username: "",
password: "",
}
handleUserChange = (event) => {
this.setState({username: event.target.value});
}
handlePwChange = (event) => {
this.setState({password: event.target.value});
}
handleSubmit = (event) => {
event.preventDefault();
return this.state.password == customData.password ? <DocumentPage /> : alert("incorrect login information")
}
render(){
return(
<div id ="test">
<form onSubmit = {()=>this.handleSubmit}>
<label for="username">User Name:</label>
<div className="ui input">
<input type="text" id="username" value = {this.state.username} onChange = {this.handleUserChange} placeholder = "Username"></input>
</div>
<label for="password">Password:</label>
<div className="ui input">
<input type="text" id="password" value = {this.state.password} onChange = {this.handlePwChange} placeholder="Password"></input>
</div>
<input className="ui blue button" type="submit" value="Submit" onClick = {this.handleSubmit}></input>
</form>
</div>
)
}
}
export default MemberPortal;
handleSubmit is an event handler, it's a function that will be triggered when you click the button, you are not supposed to return a JSX element or anything from it. A good practice is to avoid return anything from an event handler to avoid confusion.
Generally speaking, and if you are familiar with static type language such as Typescript, an event handler should have the return type as void.
As other people have already pointed out, if you can redirect to another URL if the login is successful, or if you want to do some conditional rendering within the same component, you can set a state indicating that the login is success.
A code example can be:
class MemberPortal extends React.Component {
state = {
username: "",
password: "",
isLoginSuccessful: false
}
handleSubmit = (event) => {
event.preventDefault();
// perform login function here
....
// Login not success, alert or do anything you like
if (this.state.password !== customData.password) {
this.setState({ isLoginSuccessful: false });
alert("incorrect login information");
return;
}
// Login success, perform redirect or set the boolean flag to true for conditional rendering
this.setState({ isLoginSuccessful: true });
}
....
}
I know lots of questions are related to this already on stack overflow but I am new to react and also stack overflow and I couldn't find any solution So, I'm stuck. so every time I tried to implement this code i got TypeError: this.setState is not a function error and the code is already in ES6. The error is occurring on this line : this.setState({token : true});. I don't know how to set state in there. I've bind the loginSubmit function to this but it is still not working...
import React from 'react';
import './Login.css';
import axios from 'axios';
import {Redirect} from 'react-router-dom';
class Login extends React.Component{
constructor(props){
super(props);
this.state = {
username : '',
password : '',
token : false
}
this.onChangeHandler = this.onChangeHandler.bind(this);
this.loginSubmit = this.loginSubmit.bind(this);
}
componentDidMount(){
let token = localStorage.getItem('token');
if(token!==null && token!==undefined){
this.setState({token : true});
}
}
onChangeHandler(event){
this.setState({[event.target.id]:event.target.value});
console.log(this.state.username,this.state.password);
}
loginSubmit(event){
event.preventDefault();
let loginFormData = new FormData();
loginFormData = {
'username' : this.state.username,
'password' : this.state.password
}
axios({
method : 'post',
url : 'http://localhost:3001/loginApi',
data : loginFormData
})
.then((response)=>{
if(response.data.msg=="database error"){
console.log("database error");
}
if(response.data.msg=="no user found username or password incorrect"){
console.log("no user found username or password incorrect");
}
if(response.data.msg=="jwt error"){
console.log("jwt error");
}
if(response.status==200 && response.data.result!=null && response.data.result!=undefined && response.data.token!=null && response.data.token!=undefined){
console.log("user logged in && loginapi response.data.result:",response.data.result,response.data.token);
localStorage.setItem('token',response.data.token);
this.setState({token : true});
}
})
.catch((response)=>{
console.log("catch response api call fail:",response);
localStorage.removeItem('token');
});
this.setState = {
username : '',
password : ''
}
}
render(){
if(this.state.token){
return <Redirect to='/profile' />
}
return(
<>
<div className="all">
<div className="sidenav">
<div className="login-main-text">
<h2>Application<br/> Login Page</h2>
<p>Login from here to access.</p>
</div>
</div>
<div className="main">
<div className="col-md-6 col-sm-12">
<div className="login-form">
<form>
<div className="form-group">
<label>User Name</label>
<input name="username" id="username" value={this.state.username} onChange={(event)=>this.onChangeHandler(event)} type="text" className="form-control" placeholder="User Name"/>
</div>
<div className="form-group">
<label>Password</label>
<input name="password" id="password" value={this.state.password} onChange={(event)=>this.onChangeHandler(event)} type="password" className="form-control" placeholder="Password"/>
</div>
<button name='logionSubmit' id='logionSubmit' onClick={(event)=>this.loginSubmit(event)} type="submit" className="btn btn-black">Login</button>
</form>
</div>
</div>
</div>
</div>
</>
);
}
}
export default Login;
and because of this the automatic redirection is not working I've to refresh page every time to redirect to profile. The redirection code is below.
render(){
if(this.state.token){
return <Redirect to='/profile' />
}
return( and then some jsx code goes here);
Update:
You are re-assiging this.setState when its need to be treated as immutable:
this.setState = {
username: "",
password: "",
};
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
And then calling this.setState asynchronousely in axios.
Without producible example, it may be because you didn't bind loginSubmit to this instance.
There are several ways to make sure functions have access to component attributes like this.props and this.state, depending on which syntax and build steps you are using.
// Class Properties
loginSubmit = (event) => {
...
}
Or bind it within the constructor:
// Bind in constructor
constructor(props) {
super(props)
this.loginSubmit = this.loginSubmit.bind(this);
}
It's hard to tell from your code because you need to share more of it, this could be because you need to call super(props) inside the class constructor.
I've made a modal for a simple log in page for a website:
import React from 'react';
import { withRouter } from 'react-router-dom';
import '../../assets/stylesheets/session/login_form.css';
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errors: {}
};
this.handleSubmit = this.handleSubmit.bind(this);
this.renderErrors = this.renderErrors.bind(this);
this.handleDemo = this.handleDemo.bind(this);
}
// After authentication redirect user to home page
componentWillReceiveProps(nextProps) {
if (nextProps.currentUser === true) {
this.props.history.push('/');
this.props.closeModal();
}
// Setting or clearing errors
this.setState({ errors: nextProps.errors });
}
// Hides scrolling when modal is mounted
componentDidMount() {
if (this.props.modal) document.body.style.overflow = 'hidden';
}
// Reactiviates scrolling when modal is unmounted
componentWillUnmount() {
document.body.style.overflow = 'unset';
}
// Render the session errors if there are any
renderErrors() {
return (
<ul>
{Object.keys(this.state.errors).map((error, i) => (
<li key={`error-${i}`}>{this.state.errors[error]}</li>
))}
</ul>
);
}
// Handle field updates
update(field) {
return e =>
this.setState({
[field]: e.currentTarget.value
});
}
// Handle form submission
handleSubmit(e) {
e.preventDefault();
let user = {
email: this.state.email,
password: this.state.password
};
if (this.props.errors) {
this.props.login(user)
.then(() => this.props.openModal('login'));
} else {
this.props.login(user)
.then(() => this.props.closeModal());
}
}
// Handle demo user login
handleDemo(e) {
e.preventDefault();
const user = { email: 'demouser#nookbnb.com', password: 'password' };
this.props.login(user)
.then(this.props.history.push('/'), this.props.closeModal());
}
// Rendering component
render() {
let errors;
if (this.props.errors) {
errors = this.props.errors;
} else {
errors = {};
}
let emailErrors = errors.email ? <div className="email-error">{errors.email}</div> : '';
let passwordErrors = errors.password ? <div className="password-error">{errors.password}</div> : '';
return (
<div className="login-modal-wrapper">
<div className="modal-wrapper" onClick={this.props.closeModal}></div>
<form onSubmit={this.handleSubmit}>
<div className="header-wrapper">
<div className="close-wrapper" onClick={this.props.closeModal}>
<i className="close-button"></i>
</div>
<h1>Log in</h1>
</div>
<div className="main-content-wrapper">
<button onClick={this.handleDemo}>
Demo Log in
</button>
<div className="button-separator-wrapper"><p>or</p></div>
<input
type="text"
value={this.state.email}
onChange={this.update('email')}
placeholder="Email"
/>
<input
type="password"
value={this.state.password}
onChange={this.update("password")}
placeholder="Password"
/>
<div className="session-errors">
{emailErrors}
{passwordErrors}
</div>
<button type="submit">Log in</button>
<div className="no-account-wrapper">
<p>Don't have an account? <span onClick={() => this.props.openModal('signupFirst')}>Sign up</span></p>
</div>
</div>
</form>
</div>
);
}
}
export default withRouter(LoginForm);
And I've successfully displayed the right error messages when the user doesn't enter a required field in the login form (an email and a password), but if I don't manually do a page refresh, the errors still appear on the form even after I close and reopen the modal.
How can I implement this modal in a way where it will automatically clear errors after I close and reopen the modal?
UPDATE
Per the answer below, I've added these two open and closing modal functions to help clear the errors:
// Opens a login modal
openLoginModal() {
this.setState({ errors: {} });
this.props.openModal('login');
}
// Closes a login modal
closeLoginModal() {
this.setState({ errors: {} });
this.props.closeModal();
}
And I've replaced places in the render where I'm using this.props.closeModal() and this.props.openModal() with my functions above. (For now I'm just testing this with closing the modal; since the modal doesn't have any errors when initially opened, I believe I just need to account for closing the modal right now)
<div className="login-modal-wrapper">
<div className="modal-wrapper" onClick={this.closeLoginModal}></div>
<form onSubmit={this.handleSubmit} className={errors.email && errors.email.length !== 0 ? 'form-errors' : 'form-normal'}>
<div className="header-wrapper">
<div className="close-wrapper" onClick={this.closeLoginModal}>
<i className="close-button"></i>
</div>
...
But error messages are still persisting when I open and close the modal.
Perhaps consider having an openLoginModal method that clears any errors and then opens the modal:
openLoginModal() {
this.setState({ errors: {} })
this.props.openModal('login');
}
And then replace any occurrence of this.props.openModal('login') to use this new method (this.openLoginModal()).
Edit: If you need to clear the errors specifically on exiting the modal, you can do a similar thing by creating a custom method around the closeModal prop.
I need to explain behavior of react-testing-library.
I'm trying to test my login page which works fine in browser but I am not able to pass any test.
Currently I am facing two problems:
this.props.anyFunction which is passed from test throws not a function
component is not being re-rendered on state change
Login.test.js:
let __login = render(<Login onLogin={()=>console.log('Im in!')}/>);
// fill some inputs
fireEvent.click(submit);
If user is logged in
I want just to console.log this situation at this moment but Login component throws error this.props.onLogin is not a function even if it is passed.
If user is NOT logged in
In this situation I render message in Login.js:
<div role="alert" className="text-danger px-2 mb-1">
{_(`login.error.${this.state.error}`)}
</div>
which works fine in browser. So I've tried to catch it in Login.test.js:
const alert = await __login.findByRole('alert');
which throws error Unable to find an element with the role "alert". In dump I see whole rendered login page but nothing has changed (component did not re-render after unsucessful try).
Is there anything what I didn't understand well?
Is there any way how to debug state of tested component?
Full code example
As requested I'm providing full code example. May the router or socket.io cause break?
Login.js
class Login extends React.Component
{
constructor(props) {
super(props);
this.state = {email:'', password:'', error:false}
}
componentDidMount() {
// I know this is not best way how to handle authorization, but it's just for now
this.props.io.on('onTokenResponse',(response)=> {
if (response.error) {
this.setState({error:response.error});
return;
}
if (!response.token) {
this.setState({error:'unknown error'});
return;
}
sessionStorage.setItem('token', JSON.stringify(response));
this.props.onLogin();
this.props.history.push('/');
});
}
handleSubmit=(e)=>{
e.preventDefault();
this.props.io.emit('onTokenRequest',{email:this.state.email, password:this.state.password});
};
handleChange=(e)=>
this.setState({[e.target.name]:e.target.value});
Message=()=>
this.state.error && <div role="alert" className="text-danger px-2 mb-1">{_(`login.error.${this.state.error}`)}</div>;
render() {
return (
<section className="login-box mt-5">
{this.Message()}
<form onSubmit={this.handleSubmit}>
{['email','password'].map((e)=>
<input key={e} name={e} type={e} placeholder={e} required="required"
className="form-control mb-2"
value={this.state[e]}
onChange={this.handleChange}/>
)}
<button type="submit" className="btn btn-primary text-capitalize w-100">
{_('login.submit')}
</button>
</form>
</section>
);
}
}
export default withRouter(Login);
Login.test.js
test('renders without crashing', async () =>
{
const ioClient = io.connect(config.webSocketServer);
const onLogin = jest.fn();
let __login = render(
<BrowserRouter>
<Switch>
<Login io={ioClient} onLogin={onLogin}/>
</Switch>
</BrowserRouter>
);
fillAndCheckInput('email','jaxpcze#gmail.com');
fillAndCheckInput('password','wrong');
let submit = __login.getByText('přihlásit');
fireEvent.click(submit);
const alert = await __login.findByRole('alert');
fillAndCheckInput('password','right');
fireEvent.click(submit);
await expect(onLogin).toHaveBeenCalledTimes(1);
function fillAndCheckInput(placeholder,value) {
let __input = __login.getByPlaceholderText(placeholder);
fireEvent.change(__input,{target:{value:value}});
expect(__input.value).toBe(value);
}
});