Resume prevented form submission in React - javascript

I'm implementing Stripe with React.
Upon submit click, the form submission is prevented and an ajax request goes to Stripe - giving us a token in the response which should be attached in the subsequent request to our server.
I'm stumbling on how to implement/trigger this subsequent request to our server?
Below is an example of this flow taken from the react-stripe-elements repository:
class _CardForm extends React.Component {
props: {
fontSize: string,
stripe: StripeProps,
}
handleSubmit = (ev) => {
ev.preventDefault();
this.props.stripe.createToken().then((payload) => console.log(payload));
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Card details
<CardElement
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
onReady={handleReady}
{...createOptions(this.props.fontSize)}
/>
</label>
<button>Pay</button>
</form>
);
}
}
const CardForm = injectStripe(_CardForm);

You should trigger your call to the server once you get your token, i.e in the then clause of the promise that this.props.stripe.createToken returns.
handleSubmit = (ev) => {
ev.preventDefault();
this.props.stripe.createToken()
.then((payload) => yourFetchImplementation('path/to/api/endpoint', payloadAsBody));
}

Related

Check changes before routing in React / Next js

I am having a Next JS app where there are very simple two pages.
-> Home page
import Header from "../components/header";
const handleForm = () => {
console.log("trigger");
};
export default () => (
<>
<Header />
<h1>Home</h1>
<form onSubmit={handleForm}>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<button type="submit"> Login </button>
</form>
</>
);
-> About page
import Header from "../components/header";
export default () => (
<>
<Header />
<h1>About us</h1>
</>
);
Requirement:
-> Home page has a login form
-> If user started typing in any of the fields then without submitting the form, if he tries to move to About us page then a warning needs to be displayed something similar like beforeunload_event.
I am not sure how we can handle it in react as I am new to it.. Kindly please help me to handle a alert if user trying to navigate to other url while editing the form fields..
From my understanding, you can achieve your goal by listen the event routeChangeStart as then throws exception in case of rejecting to move the target url.
I forked above codesandbox and created a simple demo based on your idea which doesn't allow to switch page in case of username having value (form is dirty).
Here is the general idea:
import router from "next/router";
export default () => {
// Assume this value holds the status of your form
const [dirty, setDirty] = React.useState();
// We need to ref to it then we can access to it properly in callback properly
const ref = React.useRef(dirty);
ref.current = dirty;
React.useEffect(() => {
// We listen to this event to determine whether to redirect or not
router.events.on("routeChangeStart", handleRouteChange);
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, []);
const handleRouteChange = (url) => {
console.log("App is changing to: ", url, ref.current);
// In this case we don't allow to go target path like this
// we can show modal to tell user here as well
if (ref.current) {
throw Error("stop redirect since form is dirty");
}
};
return (
// ...
)
}
The link codesandbox is here https://codesandbox.io/s/react-spring-nextjs-routes-forked-sq7uj

Firebase Auth Network Error from event.preventDefault() and event.stopPropagation()?

I'm getting the following error when trying to login. The email address exists on Firebase Auth and I'm able to login, but the error weirdly only happens when event.preventDefault() and event.stopPropagation(). Those two lines are listed with the comment "(UNCOMMENT AND ISSUE GOES AWAY)".
Possible Issues:
Is there something else I am missing in my code or did I make a mistake somewhere else?
Error:
Error: A network error (such as timeout, interrupted connection or unreachable host) has occurred.
Login.js
// Imports: Dependencies
import React, { useState } from 'react';
import { Button, Container, Form, Row, Col } from 'react-bootstrap';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
// Imports: Redux Actions
import { loginRequest } from '../../../src/redux/actions/authActions';
// Page: Admin Login
const Login = () => {
// React Hooks: State
const [ email, setEmail ] = useState('');
const [ password, setPassword ] = useState('');
// React Hooks: Redux
const dispatch = useDispatch();
// React Hooks: Bootstrap
const [ validated, setValidated ] = useState(false);
// React Hooks: React Router DOM
let history = useHistory();
// Login To Account
const loginToAccount = (event) => {
// Form Validation Target
const form = event.currentTarget;
// Check Form Validity
if (form.checkValidity() === false) {
// Cancels Event
event.preventDefault();
// Prevents Bubbling Of Event To Parent Elements
event.stopPropagation();
}
else {
// Validate Form
setValidated(true);
// Check If Fields Are Empty
if (
email !== ''
&& password !== ''
&& email !== null
&& password !== null
&& email !== undefined
&& password !== undefined
) {
// Credentials
const credentials = {
email: email,
password: password,
};
// Redux: Login Request
dispatch(loginRequest(credentials, history));
// // Cancels Event (UNCOMMENT AND ISSUE GOES AWAY)
// event.preventDefault();
// // Prevents Bubbling Of Event To Parent Elements (UNCOMMENT AND ISSUE GOES AWAY)
// event.stopPropagation();
}
}
};
return (
<div>
{/* <NavigationBar /> */}
<Container id="login-container">
<div id="login-inner-container">
<div id="login-logo-container">
<p id="login-title">Login</p>
</div>
<Form validated={validated} onSubmit={(event) => loginToAccount(event)}>
<Form.Group controlId="login-email">
<Form.Label className="form-field-title">Email</Form.Label>
<Form.Control
type={'email'}
placeholder={'Email'}
pattern={'[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,}$'}
onChange={(event) => setEmail((event.target.value).toLowerCase())}
value={email}
maxLength={50}
required
/>
<Form.Control.Feedback type="invalid">Invalid email</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="login-password">
<Form.Label className="form-field-title">Password</Form.Label>
<Form.Control
type={'password'}
placeholder={'Password'}
onChange={(event) => setPassword(event.target.value)}
value={password}
maxLength={40}
required
/>
<Form.Control.Feedback type="invalid">Required</Form.Control.Feedback>
</Form.Group>
<Button
variant="primary"
type="submit"
id="login-button"
onClick={(event) => loginToAccount(event)}
>Login</Button>
</Form>
</div>
</Container>
</div>
)
};
// Exports
export default Login;
You are registering your `` function as the submit handler for a form:
<Form validated={validated} onSubmit={(event) => loginToAccount(event)}>
When a HTML form is submitted, its default behavior is to send the data to the server as a request that navigates away from the current page. The logic here is that the server handles the request, and send a response to the client that it then renders. That's pretty much how the web worked 20+ years ago, hence it being the default behavior for HTML forms.
So with the commented out preventDefault, your code starts signing in to Firebase and then immediately navigates away (most likely just reloading the same page). This interrupts the sign-in, which is why you see the error message.
By calling event.preventDefault() you indicate that you want to prevent the default behavior (the submitting of the form to the server), since your code is handling that itself (by calling loginRequest).
Calling stopPropagation stops the browser from giving parent HTML elements the chance to act on the event. It typically shouldn't be needed to prevent the form submission, but depends a bit on the HTML that is generated.

ReactJS: How do i display the retrieved post json response to the same form component which i used to submit the value?

thank you in advance for helping.
I am working on a crawler which I want to work in the following steps:
The user enters the seed url (front-end)
The user press the submit button (front-end)
The seed url will be processed at the express backend which will return a response in json (backend)
Once the seed url has been processed at the backend, json response will be sent back to the same component form and populate the input with the state object name (front-end)
I am currently having trouble with step no.4 where I managed to return the retrieved post json response from express backend but I'm not sure how to make it display back in the component after the handleSubmit function.
Here is my form:
import React from 'react';
import axios from 'axios';
class Crawler extends React.Component {
constructor(props) {
super(props);
this.state = {
seedURL: '',
};
}
handleInputChange = (e) => {
e.preventDefault();
let name = e.target.name;
let value = e.target.value;
// Do a set state
this.setState({
[name]: value
});
};
handleSubmit = (e) => {
e.preventDefault();
// The seed url taken from the input
const seedURL = this.state;
console.log("SEED URL: ", seedURL);
// Take the seedURL and send to API using axios
const url = "/api";
// Send data using axios
axios.defaults.headers.post['Content-Type'] ='application/x-www-form-urlencoded';
axios.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
try {
axios
// url - represents the backend url from express
// seedURL - is the seed url that we want to send to the backend
.post(url, seedURL)
.then((res) => {
console.log(res);
console.log(res.data);
})
.catch((err) => {
console.log(err);
console.log(err.data);
});
} catch (e) {
console.log(e);
}
};
state = {
loading: true,
retail: null
}
render() {
return(
// Start of crawler form
<div className="crawler-form">
<form onSubmit={this.handleSubmit}>
<h2>Web Crawler</h2>
<div className="url">
<input
type="text"
placeholder="SEED URL"
name="seedURL"
onChange={this.handleInputChange}
/>
</div>
<input type="submit" value="Crawl" />
</form>
<h3>Business Information</h3>
<div className="retail-style">
<div className="name">
<input type="text" placeholder="Business Name" name="name" value={/* how do i retrieve the value */} />
</div>
</div>
</div>
// End of Crawler form
);
}
}
export default Crawler;
React will re-render your component any time the state changes. So the easy way to do this would be to add a property to the state that will hold the response:
this.state = {
seedURL: '',
response: null,
error: null,
};
Then, when you get the response from the request, you update the state:
axios
.post(url, seedURL)
.then((res) => {
this.setState({response: res, error: null});
})
.catch((err) => {
this.setState({error: err, response: null});
});
And then in your render method, you'll need to do something with the response:
render(){
const bizName = this.state.response ? this.state.response.data.name : "";
return(
//... other jsx
<div className="name">
<input type="text" placeholder="Business Name" name="name" value={bizName} />
</div>
);
}
This is how to update the input value based on the response:
...
.then((res) => {
document.querySelector('input[name="name"]')[0].value = response.data;
})
...
I assume that response.data is a text value, if its not, then you have to provide more information about response.data. like providing console.log(response.data) in your post.
You can use async/await for the promise.
First, make the handleSubmit method async:
handleSubmit = async (e) => {...
Then make sure you wait for the response from post request:
await response = axios.post(url, seedURL);
and set the data in the response to the component state.
this.setState({ value: response.data.value });
Finally, make sure use use the value in the state:
<input type="text" placeholder="Business Name" name="name" value={this.state.value} />

How to run onSubmit={} only after the props have updated?

I want to run onSubmit on my form only after my props that I receive from the reducer update.
const mapStateToProps = state => {
return {
authError: state.auth.authError // I want this to update before the action.
};
};
If I console.log authError in onSutmit handler I receive the old authError. The second time I run it, I receive the updated authError.
handleSubmit = e => {
e.preventDefault();
console.log("error exists?", this.props.authError);
this.props.signIn(this.state); //dispatches a login action
this.handleHideLogin(); // hides the form after action is dispatched
};
I want to hide the form only after the action is dispatched and the error is null. (it returns null automatically if the authentication succeeds)
I tried using setTimeout() and it technically works, but I want to know if there is a more "proper" way to do it.
handleSubmit = e => {
e.preventDefault();
this.props.signIn(this.state);
setTimeout(() => {
if (this.props.authError) {
console.log("returns error =>", this.props.authError);
} else {
console.log("no error =>", this.props.authError);
this.handleHideLogin();
}
}, 500);
};
part of my component for reference
<form onSubmit={!this.props.authError ? this.handleSubmit : null}>
//the above onSubmit is different if I use the setTimeout method.
<div className="modal-login-body">
<div className="modal-login-input">
<input
type="email/text"
name="email"
autoComplete="off"
onChange={this.handleChange}
required
/>
<label htmlFor="email" className="label-name">
<span className="content-name">EMAIL</span>
</label>
</div>
<div className="modal-login-input">
<input
type="password"
name="password"
autoComplete="off"
onChange={this.handleChange}
required
/>
<label htmlFor="password" className="label-name">
<span className="content-name">PASSWORD</span>
</label>
</div>
</div>
<div className="my-modal-footer">
<p className="login-failed-msg">
{this.props.authError ? this.props.authError : null}
</p>
<button type="submit" className="complete-login-button">
LOGIN
</button>
<a href="CHANGE" className="forgot-password">
<u>Forgot your password?</u>
</a>
</div>
</form>
I am assuming that this.props.signIn is an async function.
And thus this.props.authError is updated asynchronously and that's why if you setup the timeout it works in some cases (when you get the response shorter than 5 seconds).
One way to solve it is using then and catch without updating the state of the form
handleSubmit = e => {
e.preventDefault();
this.props.signIn(this.state).then(resp => {
this.setState({userIsValid: true, failure: null})
this.onUpdate(userIsValid);
})
.catch(err => {
this.setState({userIsValid: false, failure: "Failed to login"})
});
}
and use if-else to determine whether to show the form or to display your website
class App extends React.Component {
render() {
if (this.state.isValidUser) {
return <Main />
} else {
return <LoginForm onUpdate={(isValid) => {this.setState({isValidUser: isValid})} />
}
}
}
In other words, the LoginForm component stores username, password, failure (error message why login failed) and isValidUser (to determine if login is successful).
The App has a state to determine what to show, the website or the login component. Using onUpdate that is passed as props to the login component we can update the state of App and show the website if login is successful.
I hope it helps.
I solved this issue by conditonal rendering on the entire component.
I used Redirect from react-router-dom to just not render the element if I'm logged in.
If I'm logged in I don't need to display the login form anyway.
if (this.props.loggedOut) {
<form onSubmit={this.handleSubmit}>
//...
</form>
} else {
return <Redirect to="/" />;
}

React delay button event for 2 seconds

<input
type="file"
ref={ref => {this.ref = ref}}
/>
<button
onClick={(e)=> {
const promise = new Promise((resolve) => {
setTimeout(() => { resolve() }, 2000) //change to 1000 works
})
promise.then(()=> {
console.log('clicking');
this.ref.click();
})
}}
>
Click me
</button>
When the Click me button is clicked, I need to do some additional actions (eg. prompt for facebook login or fetch data) before opening the input file explorer.
In this case, i set a 2 sec delay but the click no longer activates. Changing the delay to 1 sec actually works.
I supposed the event 'expired' after 1 sec so there's nothing i can do for my situation apart from making the user click again?
Your code shows you are clicking on a file input in the resolve method. And the click is executing in a resolve method of a promise.
Please note that you cannot trigger click on file input from a function that is not directly initiated by a user action. Say the click is trigger on onClick, then click on file would be executed. Since you are executing with a delay on a function that is not related to user initiated action, the click on file input wont be triggered. This is for security concerns.
But in your question since you are mentioning that you want prompt for facebook login or fetch data which will work as they might be pure javascript related actions.
I assume you want to "block" the execution of the file event until you perform some logic like log-in.
I would tackle it from a different angle, on the click event of the "dummy" button i would open a modal or a login page / component asking to login while passing it another event handler.
On the login form submit event i will check my logic of authentication and only then decide if i want to trigger the open-file dialog event.
a very simple example based on the code you provided:
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
const LoginPage = ({ onClick }) => {
return (
<div>
<h1>Please login!</h1>
<input type="text" placeholder="user name"/>
<input type="password" placeholder="password" />
<button type="button" onClick={onClick}>Login</button>
</div>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showLogin: false
};
this.onBtnClick = this.onBtnClick.bind(this);
this.onLogin = this.onLogin.bind(this);
}
onBtnClick(e) {
// if not logged in...
this.setState({showLogin: true});
}
onLogin(data) {
// check log in success logic etc...
// if success
this.setState({showLogin: false});
this.ref.click();
}
render() {
const {showLogin} = this.state;
return (
<div style={styles}>
<input
type="file"
ref={ref => {
this.ref = ref;
}}
/>
<button onClick={this.onBtnClick}>Click me</button>
{showLogin && <LoginPage onClick={this.onLogin} />}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Categories

Resources