Render a new component on the same page - reactjs

I have a form component, and upon filling this form (click submit button), I would like to render a new component in place of the form, on the same page, with some comment like "congratulations for filling the form" for instance.
I am not sure what is the right way to achieve this. I am not sure it is right to do a conditional rendering here.
Also, should this be done on the page where I render the form component, or in an action creator ?
Thank you for your kind help !

It is a sample code of how to do it:
export var LoginBox = React.createClass({
getInitialState(){
return{
registered:false
}
},
Register(){
//submit registration form and change form to login form
...
this.setState({registered : true});
},
login(){
// submit login form here
},
loginForm(){
var{registered} = this.state;
if(registered){// your registeration form
return(
<form onSubmit={this.Register}>
<div><input ref="number" type="tel"/></div>
<button type="submit">register</button>
</form>
);
}else{
return (// your login form
<form onSubmit={this.login}>
<div><input type="number" ref="activationCode"/></div>
<button type="submit">login</button>
</form>
);
}
}
},
render(){
return (
<div className="loginBox">
{loginForm()}
</div>
)
}
});
It is a sample code. First, you need to initialize a state for default form. Then, on form submit change the state in order to change form on your view.
I hope it helps you to understand it.

Related

How to validate the form and send user to another page on successful data filled in form fields in React JS?

I want to send a user to Successful component of ReactJS after form with user and password has been filled up.
Here is the code:
import React from 'react';
import Successful from './Successful';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
user: '',
pass: ''
}
}
onsubmit = () => {
<Successful/>
}
render() {
return (
<form onSubmit={this.onsubmit}>
<div className="App">
<input type="text"
placeholder="Enter user"
validate="required"
onChange={() => this.state.user}
/>
<input type="password"
placeholder="Enter password"
validate="required"
onChange={() => this.state.password}
/>
<button type="submit">Submit</button>
</div>
</form>
)
}
}
export default App;
What I undestand you want to do is validate the password to see if it matches a user's password, if that happends, then redirect the user to another page? In that case, what you are doing good. Your validation logic should be located inside your onsubmit method, which triggers automatically when the user submits the form.
Inside that method you first, you should catch your form submit event, to prevent your page from reloading (which is the default behaviour when submitting a form). To do this, you receive the event object (which is passed automatically), and call it's preventDefault method:
onsubmit = (event) => {
event.preventDefault() // To prevent page reloading...
}
Here you check wheter the password entered is correct with a simple conditional, and then, there is the redirection part. I see you are just returning the component you wanna render (<Successful />). If what you wanna do is just show this component on the same page, you should add a new state property, that controls wheter the form input was successful or not, and then show or hide that component based on that state:
// Add the success prop
this.state = {
user: "",
pass: "",
success: false
}
onsubmit = (event) => {
event.preventDefault()
// Check if your password is valid...
// If its valid, then:
this.state.success = true
}
render() {
return (
<div>
... your other stuff here ...
{this.state.successful && <Successful />}
</div>
)
}
What {this.state.successful && <Successful />} does is that it only renders the component after &&, if the condition is true. Because of your validation process, this.state.successful is only true if the entered password passed the validation process, so your success component gets rendered only if the condition is fulfilled. The brackets in {this.state.... && <Successful />} are required.
If what you want to do is really redirect your user to another page, you should use something like react-router-dom (a library that allows you to do redirecting) and instead return its <Redirect /> component or browserHistory method. Its really too long to explain here, but you can check this or look for some tutorial on react-router because is an extensive library.
import React from 'react';
import Successful from './Successful';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
user: '',
pass: '',
success: false
}
}
onsubmit = () => {
this.state.setState({success:true})
}
const form = <form onSubmit={this.onsubmit}>
<div className="App">
<input type="text"
placeholder="Enter user"
validate="required"
onChange={() => this.state.user}
/>
<input type="password"
placeholder="Enter password"
validate="required"
onChange={() => this.state.password}
/>
<button type="submit">Submit</button>
</div>
</form>
render() {
return (
{!this.state.success ? form :
<Successful/>}
}
export default App;
Something like this could work. Here I added a "success" variable to the state. Initally set as false so the "form" is return and rendered.
onSubmit we set the state to true, this triggers a re-render and then we return the componant instead.
(I think I set the state correctly, I usually use hooks these days)
import { useHistory } from "react-router-dom"; //this goes on top
const history = useHistory();
onsubmit = () => {
//your logic for user input validation goes here
if(input is validated) {
history.push("/success");
}
else {
//show error here
}
}
Since you have added react-router in the tags. I am assuming that you have a route with success componenent.

form submit re-renders page in react

I have a form in a react component.
When I click the submit button, I call signin(). In signin(), an error occurs as I can see the output in the chrome console, but it flashes by so quickly, I can't read it. Then the page refreshes and the error message is gone from the console.
Why is my form re-rendering the page? And how can I suppress the re-rendering so that I can read the error?
Here is my component:
import React, { Component } from 'react';
import './login.scss';
import axios from '../axios/axiosInstance';
class Login extends Component {
constructor() {
super();
this.usernameRef = React.createRef();
this.passwordRef = React.createRef();
}
signin() {
axios.get('/auth', {headers: {
username: this.usernameRef.current.value,
password: this.passwordRef.current.value}}).then(response => {
console.log('response=', response);
}).catch(err => {
console.log('err=', err);
});
}
render() {
return (
<div className="login-container">
<form onSubmit={this.signin}>
<div className="flex-row-end">
<div className="form-element flex-column-end">
<input type="text"
placeholder="Username or email"
name="username"
ref={this.usernameRef}
required />
</div>
<div className="form-element flex-column-end">
<input type="password"
placeholder="Password"
name="password"
ref={this.passwordRef}
required />
</div>
<div className="login-submit">
<button className="submit-login-button" type="submit"><i className="fas fa-sign-in-alt"> </i>Sign In</button>
</div>
</div>
</form>
</div>
);
}
};
export default Login;
As you can see, in signin(), I am using axios to send a request to my backend with user credentials. On the backend, the logs show no record of receiving the request. Therefore, the error must be happening before the request is sent. But I need a way to suppress the re-rendering of the page so that I can see what the error message is.
Thank you very much.
Change the signature of signin to take the form submission event, you'll need to prevent the default action on it. This keeps the page from reloading.
signin(e) {
e.preventDefault();
...
The error tells you that reference from component is not define yet.
Because method is not bound to this when used as a function:
<form onSubmit={e => this.signin(e)}>
and then put e.preventDefault() inside signin(e) which prevent the blink after you submit the form.
If I'm not mistaken axios will make the http request asynchronous, so you might have to use the event.persist() if this is not the case, preventDefault() should work as they mentioned in the answers above, also it is recommended to call the api in the componentDidMount()

passing down functions correctly for onSubmit and handleChange on a form

I am trying to handle navigation around my application. I want to include a link back to the form component from displayweather using conditional statements. I can get the form to show correctly, however when you press submit on the form it just refreshes back to the Form under App
I am trying to pass the functions down from App to Navbar which holds a second instance of the Form
However, using react dev tools the functions are still showing as undefined?
Here is a gist of the code:
https://gist.github.com/dhuang612/6c683b6ccd0ce6299631db76ed76ccd7
The two functions I am trying to hand down from App
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
//handles submit on the form and runs the api call
onSubmit = e => {
e.preventDefault();
this.fetchWeatherData();
this.setState({ fetchedweatherdata: !this.state.fetchedweatherdata }, () =>
console.log(this.state.fetchedweatherdata)
);
};
this is how I am trying to pass the functions down to this second instance of form
<DisplayWeather
currentweather={this.state.currentweather}
currentforecast={this.state.currentforecast}
currenttime={this.state.currenttime}
weatherIcon={this.state.weatherIcon}
hourlyWeather={this.state.hourlyWeather}
/>
<Navbar fetchedweatherdata={this.state.fetchedweatherdata}>
<Form
onChange={this.handleChange}
{...this.state}
onSubmit={this.onSubmit}
/>
</Navbar>
and this is how I am trying to call the functions on the form
return (
<div>
{!this.state.fetchedweatherdata ? (
<div>
<button onClick={this.resetState}>return to form</button>
</div>
) : (
<Form onChange={handleChange} {...this.state} onSubmit=
{onSubmit} />
)}
</div>
I am not getting any error messages, the screen refreshes and reloads the origin Form under App, I would like the application to re-send out the api request and show current weather for the new location. Please see gist to see all the code together.
Thanks for any help!
The issue was that you did not pass nothing to Navbar itself.
I did put a codesandbox up for you, please, for the future question if there is more code included try to do the same with codesandbox, this can save lifes!
Here is the mentioned sandbox

How to listen to localstorage value changes in react?

I want to show a button when user is logged.If user is not logged then I m not showing button.When user logged i will set local storage values.when i set local storage in login Component,Header component must listen to that event and show the button.I m using addEventListener for listening.But its not listening.
I don't know where to listen in header Component.
// HeaderComponent(header.js):
class HeaderComponent extends Component {
componentDidMount(){
if(typeof window!='undefined'){
console.log(localStorage.getItem("token"));
window.addEventListener("storage",function(e){
this.setState({ auth: true});
})
}
}
render() {
return (
<div className="header">
<div className="container">
<div className="header-content">
<img src={logo} alt="logo"></img>
<div className="nav-links" >
<ul >
<li>Home</li>
<li>About</li>
<li>Services</li>
<li><NavLink activeClassName="active" to="/upload" >Upload</NavLink></li>
<li><NavLink activeClassName="active" to="/signup"> Sign Up</NavLink></li>
{ this.state.auth? <li onClick={this.onLogout}>Logout</li> :null}
</ul>
</div>
</div>
</div>
</div>
);
}
}
//loginComponent(login.js)
class LoginComponent extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(event) {
const data = {
username: document.getElementById('name').value,
password: document.getElementById('password').value
}
axios.post(`http://localhost:4000/user/login`, data).then(res => {
this.props.history.push("/");
localStorage.setItem("token",res.data.token);
localStorage.setItem("auth",true);
}).catch(err => console.log(err));
}
render() {
return (
<section class="log-in">
<div class="card-col">
<form>
<h3>LOG IN</h3>
<div class="form-controls">
<input id="name" type="text" placeholder="username" class="input"></input>
</div>
<div class="form-controls">
<input id="password" type="password" placeholder="password" class="input"></input>
</div>
<button type="submit" onClick={this.onSubmit} class="button" >Log in</button>
</form>
</div>
</section>
)
}
}
The current answers are overlooking a really simple and secure option: window.dispatchEvent.
Where you set your localStorage item, if you dispatch an event at the same time then the eventListener in the same browser tab (no need to open another or mess with state) will also pick it up:
const handleLocalStorage = () => {
window.localStorage.setItem("isThisInLocalStorage", "true");
window.dispatchEvent(new Event("storage"));
};
window.addEventListener('storage', () => {
console.log("Change to local storage!");
// ...
})
EDIT:
Because this seems to be helpful, I'd also recommended checking out the useLocalStorage hook from the usehooks-ts team. You don't need to install it as a package; you can just copy the hook wholesale. This hook makes use of the solution I originally shared, but adds a whole lot more sophisticated logic to it.
Please take note of two things
storage event works only when the same application opened in two browser tabs (it is used to exchange info between different tabs of the same app). Storage event will not fire when both components shown on the same page.
When adding event listerner, you're passing function(), not array function. function() doe not capture this so you should explicitly bind(this) or change it to arrow function.
For example
window.addEventListener("storage",(function(e){
this.setState({ auth: true});
}).bind(this));
Or do with arrow function
window.addEventListener("storage",(e) => {
this.setState({ auth: true});
});
Here is simple example.
Be sure to open it in two tabs (the same link). Store value in one tab and see this value in another tab.
I found a really bad hack to accomplish this:
I have a Toolbar and a Login Component where the Toolbar component listens to changes in localStorage and displays the logged-in user name when the Login Component updates local storage if authentication is successful.
The Toolbar Component
(similar to the Header component in your case)
const [loggedInName, setLoggedInName] = useState(null);
useEffect(() => {
console.log("Toolbar hi from useEffect")
setLoggedInName(localStorage.getItem('name') || null)
window.addEventListener('storage', storageEventHandler, false);
}, []);
function storageEventHandler() {
console.log("hi from storageEventHandler")
setLoggedInName(localStorage.getItem('name') || null)
}
function testFunc() {
console.log("hi from test function")
storageEventHandler();
}
Add a hidden button to your Toolbar component. This hidden button will call the testFunc() function when clicked which will update the logged-in user's name as soon as local storage is updated.
<button style={{ display: 'none' }} onClick={testFunc} id="hiddenBtn">Hidden Button</button>
Now, in your Login component
.
.
.
//login was successful, update local storage
localStorage.setItem("name",someName)
//now click the hidden button using Javascript
document.getElementById("hiddenBtn").click();
.

Refreshing issue when submitting form with react-redux-form

Issue
Important: This has been solved; see below explanation.
I've been having this issue of the page refreshing when submitting a react-redux-form. I have found a lot of similar issues since this is the default behavior of submitting a form in HTML; however I haven't found anything related to this specific library (react-redux-form).
I've also tried to apply what was suggested in other cases, mainly the event.preventDefault(), but I don't know how to use the event object with this library as they recommend the following syntax:
<LocalForm onSubmit={(values) => this.handleSubmit(values)}>
I've tried with values.event, but that was unsuccessful.
Below what I intend to have:
import React, { Component } from 'react';
import Hall from './HallComponent';
import { Row, Button, Label } from 'reactstrap';
import { Redirect } from 'react-router';
import { LocalForm, Control } from 'react-redux-form';
class Endgame extends Component {
constructor(props) {
super(props);
this.state = {
redirect: false
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(values) {
const date = new Date();
this.props.addScore({
date: date.toLocaleDateString("en-GB"),
name: values.name,
score: this.props.points
});
this.setState({ redirect: true });
}
render() {
if (this.state.redirect) {
return (
<Redirect to="/hall" />
);
}
else {
return(
<div className="container">
<div className="row">
<div className="col-12 col-md-5 m-1">
<p>Your score is: {this.props.points}</p>
<p>Add you name to the Hall of Fame</p>
<LocalForm onSubmit={(values) => this.handleSubmit(values)}>
<Row className="form-group">
<Label htmlFor="name">Nickname</Label>
<Control.text model=".name" id="name" name="name" placeholder="Your Name" className="form-control" />
</Row>
<Button type="submit">Submit</Button>
</LocalForm>
</div>
<div className="col-12 col-md-5 m-1">
<Hall scores={this.props.scores} />
</div>
</div>
</div>
);
}
}
}
export default Endgame;
Solution
I've found that the problem was not coming from the submit button but the way I had arranged my components.
I'm still not entirely sure of what actually happens behind the scene, but my Hall component above was unmounted and re-mounted each time the addScore() e.g. a redux action was fired. I found out that this could happen if the state of the parent component was modified somehow this could trigger re-mounting of children components.
I've migrated the state locally to Hall component connecting it to redux store and it now works properly.
its because you have a button of type "submit". Its default action is to refresh the page.
<Button type="submit">Submit</Button>
You can put an evt.preventDefault() in your handle submit function to stop the page from refreshing, but I would encourage you to just change the button type and put your handleSubmit function on the onClick event like this.
<Button type="button" onClick={this.handleSubmit}>Submit</Button>

Resources