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

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.

Related

When button type is "submit", useState hook doesn't fire

As the title suggests, I have a form component that has an onSubmit prop set to handle form submissions. Typically in my application, when a user submits a form, a long running process starts. During that time I want to display a spinner/disable the button to stop the user from submitting the form while the query loads.
However, when the submit button type is submit, any state setters that come from the useState hook (or even a Redux dispatcher) don't seem to change the state.
I see that the component is re-rendered when the submit handler is invoked, even though I used event.preventDefault() and event.stopPropagation(). How can I prevent this re-render from occurring and messing with the state?
My code is below, and I have create a minimum reproduction in a Code Sandbox here. I utilized a setTimeout function to mimic the API call that actually happens.
import React, {
ChangeEvent,
FormEvent,
FunctionComponent,
useRef,
useState
} from "react";
import { Button, Form, Modal, Spinner } from "react-bootstrap";
const SimpleForm: FunctionComponent = () => {
// form state
const [name, setName] = useState("TestName");
const [age, setAge] = useState("10");
// form state helpers
const [submitLoading, setSubmitLoading] = useState(false);
const formRef = useRef<HTMLFormElement | null>(null);
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
event.stopPropagation();
const form = event.currentTarget;
if (form.checkValidity()) {
setSubmitLoading(true);
console.log("valid form");
setTimeout(() => console.log("blah"), 5000);
setSubmitLoading(false);
}
};
return (
<Modal centered size="lg" show={true}>
<Form ref={formRef} onSubmit={handleSubmit}>
<Modal.Header closeButton>
<Modal.Title>Simple Form</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form.Group className="mb-3" controlId="instanceName">
<Form.Label as="h6">Name</Form.Label>
<Form.Control
required
type="text"
placeholder="Instance name here"
value={name}
spellCheck="false"
autoComplete="off"
onChange={(e) => {
setName(e.target.value);
}}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="diskSize">
<Form.Label as="h6">Age</Form.Label>
<Form.Control
type="number"
min="10"
max="1000"
value={age}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setAge(e.target.value)
}
/>
</Form.Group>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary">Close</Button>
<Button
type="submit"
variant="success"
style={{
padding: submitLoading ? "0.375rem 3.875rem" : "0.375rem 0.8rem"
}}
>
{submitLoading ? (
<Spinner size="sm" animation="border" />
) : (
"Submit Form"
)}
</Button>
</Modal.Footer>
</Form>
</Modal>
);
};
I have tried replacing the inside of the function with a closure with and without the previous state (() => true / (prevState) => !prevState). I tried replacing the useState hook with a useRef hook, and setting the ref manually inside the event handling function. I also moved the state into Redux, and that didn't seem to do anything either. I also tried adding an onClick prop to the button with a closure inside, and that seems to do nothing either.
Any help would be much appreciated!
You're setting the loading state to false immediately after setting it to true, thus you don't see any changes. If you move the setSubmitLoading(false) INSIDE the setTimeOut, the changes will reflect for you, as it did for me on the sandbox.
const handleSubmit = () => {
console.log(`submitLoading in handler: ${submitLoading}`);
setSubmitLoading(true);
console.log("valid form");
setTimeout(() => setSubmitLoading(false), 5000);
};
One problem would be that the setTimeout does nothing here, as it runs 5000ms later as the setSubmitLoading function, js basically "ignores" the setTimeout here unless you use await, which makes it wait for the function to complete (using await for the actual api request, it doesnt work on setTimeout)
which means React updates the state instantly not really changing anything
this suggests that maybe your onSubmit function doesnt use await correctly, or .then Promise callbacks
asyncFunction().then(() => setState(false))

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

multi step form in react taking wrong validation

I am writing my problem as a fresh part here.
I made a multi step form where I have on dynamic filed in 1st form, that field is to create password manually or just auto generated.
So my multi step form is working fine going to and forth is fine, but I have to pass the fields to main component so it can check for validation, and I am passing that password too
Here comes the issue
When i pass the password field also then it takes the validation even when I have click on auto generated password
I am passing fields like this fields: ["uname", "email", "password"], //to support multiple fields form
so even not check on let me create password it takes the validation.
When i click let me create password and input some values then click on next and when I comes back the input field sets to hidden again to its initial state I know why it is happening, because when I come back it takes the initial state allover again.
i am fed-up with this thing for now, I have tried many things but didn't work below is my code
import React, { useState, useEffect } from "react";
import Form1 from "./components/Form1";
import Form2 from "./components/Form2";
import Form3 from "./components/Form3";
import { useForm } from "react-hook-form";
function MainComponent() {
const { register, triggerValidation, errors, getValues } = useForm();
const [defaultValues, setDefaultValues] = useState({});
const forms = [
{
fields: ["uname", "email", "password"], //to support multiple fields form
component: (register, errors, defaultValues) => (
<Form1
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
},
{
fields: ["lname"],
component: (register, errors, defaultValues) => (
<Form2
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
},
{
fields: [""],
component: (register, errors, defaultValues) => (
<Form3
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
}
];
const [currentForm, setCurrentForm] = useState(0);
const moveToPrevious = () => {
setDefaultValues(prev => ({ ...prev, ...getValues() }));
triggerValidation(forms[currentForm].fields).then(valid => {
if (valid) setCurrentForm(currentForm - 1);
});
};
const moveToNext = () => {
setDefaultValues(prev => ({ ...prev, ...getValues() }));
triggerValidation(forms[currentForm].fields).then(valid => {
if (valid) setCurrentForm(currentForm + 1);
});
};
const prevButton = currentForm !== 0;
const nextButton = currentForm !== forms.length - 1;
const handleSubmit = e => {
console.log("whole form data - ", JSON.stringify(defaultValues));
};
return (
<div>
<div class="progress">
<div>{currentForm}</div>
</div>
{forms[currentForm].component(
register,
errors,
defaultValues[currentForm]
)}
{prevButton && (
<button
className="btn btn-primary"
type="button"
onClick={moveToPrevious}
>
back
</button>
)}
{nextButton && (
<button className="btn btn-primary" type="button" onClick={moveToNext}>
next
</button>
)}
{currentForm === 2 && (
<button
onClick={handleSubmit}
className="btn btn-primary"
type="submit"
>
Submit
</button>
)}
</div>
);
}
export default MainComponent;
please check my code sand box here you can find full working code Code sandbox
React Hook Form embrace native form validation, which means when your component is removed from the DOM and input state will be removed. We designed this to be aligned with the standard, however we start to realize more and more users used to controlled form get confused with this concept, so we are introducing a new config to retain the unmounted input state. This is still in RC and not released.
useForm({ shouldUnregister: true })
Solution for now:
break into multiple routes and store data in the global store
https://www.youtube.com/watch?v=CeAkxVwsyMU
bring your steps into multiple forms and store data in a local state
https://codesandbox.io/s/tabs-760h9
use keepAlive and keep them alive:
https://github.com/CJY0208/react-activation

Run some code in React after multiple async useState setters

I have a functional React component in which I am using useState to manage state. Normally, it's just a form with two fields - code and env - which the user can manually fill out and submit. However, when the component loads, I also want to check any querystring params and if the appropriate ones exist, I want to populate and submit the form automatically. That way, users can bookmark specific form submissions.
The problem I'm having is that, as we all know, useState setters are async, just like setState in class components. As both form fields are controlled by state, setting both values will kick off multiple renders, so where should I put the code to submit the form so that I'm guaranteed that both state updates have completed?
Here is the form:
And here is a simplified, sanitized version of the code I have:
import React, { useState, useEffect } from "react";
import axios from "axios";
import queryString from "query-string";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import ToggleButtonGroup from "react-bootstrap/ToggleButtonGroup";
import ToggleButton from "react-bootstrap/ToggleButton";
import Card from "react-bootstrap/Card";
/*
* this component will show a spinner or the results from the API when complete
*/
const PortalDisplay = ({ data: portal, isLoading }) => {
if (Object.keys(portal).length === 0 && !isLoading) {
return null;
} else if (isLoading) {
return (
<div>
<p>loading…</p>
</div>
);
} else if (!!portal.id && !isLoading) {
return <div className="card-portal">data goes here</div>;
}
};
/*
* main component
*/
const PortalConfiguration = () => {
const [validated, setValidated] = useState(false);
const [code, setCode] = useState("");
const [env, setEnv] = useState("prod");
const [portalInfo, setPortalInfo] = useState({});
const [isLoading, setIsLoading] = useState(false);
const params = queryString.parse(location.search);
const onSubmitForm = (event) => {
const form = event.currentTarget;
setValidated(true);
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
return;
}
//re-initialize
setIsLoading(true);
setPortalInfo({});
axios
.get(`http://www.example.com/api`)
.then((response) => {
setIsLoading(false);
setPortalInfo({ ...response.data, ...{ baseUrl: baseUrl[env] } });
})
.catch((error) => console.log(error));
event.preventDefault();
event.stopPropagation();
};
useEffect(() => {
if (!!params && !!params.portal && !!params.env) {
if (!/^[a-zA-Z]{3}$/.test(params.portal) || (params.env !== "prod" && params.env !== "demo")) {
console.log(`Specified portal parameters {portal: ${params.portal}} and {env: ${params.env}} are incorrect`);
} else {
// this is where i want to set the portal and env states and submit the form
}
}
}, [params.portal]);
return (
<>
<h1>Your Portal Configuration</h1>
<Card>
<Card.Body>
<Form noValidate validated={validated} inline className="form-portal" onSubmit={onSubmitForm}>
<Form.Group className="form-group-code">
<label className="sr-only" htmlFor="code">
Portal Code
</label>
<Form.Control
type="text"
id="code"
value={code}
required
placeholder="Enter Portal Code (e.g. 'FWU')"
maxLength="3"
onChange={(e) => setCode(e.target.value)}
/>
<Form.Control.Feedback type="invalid">Portal Code must be three letters (a-z)</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<ToggleButtonGroup type="radio" name="env" value={env} onChange={(val) => setEnv(val)}>
<ToggleButton type="radio" name="env" value="prod" variant="primary" className="btn-inline">
Production
</ToggleButton>
<ToggleButton type="radio" name="env" value="demo" variant="primary" className="btn-inline">
Demo
</ToggleButton>
</ToggleButtonGroup>
</Form.Group>
<Button variant="secondary" block="true" className="btn-inline" type="submit">
Submit
</Button>
</Form>
</Card.Body>
</Card>
<PortalDisplay data={portalInfo} isLoading={isLoading} env={env} />
</>
);
};
export default PortalConfiguration;
The line which is commented out and says "this is where i want to set the portal and env states and submit the form" is where I know I have querystring params passed and need to set both states, then submit the form.
FWIW, I have considered the usual answer to the question of how to deal with useState's asynchronicity, which is to handle it in useEffect, scoped to the particular state variable you are interested in. The two problems with that is that 1) I have two state variables that need to be updated so I don't think there's any guarantee that they will be updated in the order I called the setters, creating a possible race condition, and 2) I don't want to call this code every time that code or env updates, which can happen when the user manually interacts with the form. I only want it to be auto-submitted when the component detects the querystring upon loading.

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="/" />;
}

Categories

Resources