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

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

Related

"Cannot read property 'length' of undefined" on an empty array

Hi I'm new to Node/React, and I'm creating a learning project. It's platform that connects freelancers with nonprofit companies. Users (freelancers) view a list of companies, and click a button to connect to a company. Once this is clicked, the user will have that company added as a relationship in the database. This is working correctly.
Now I'm trying to have a page where the user can view all their connections (the companies they connected with). The solution below works but only if the user has at least one connection. Otherwise, I get the error Cannot read property 'length' of undefined.
To figure out which JSX to render, I'm using a conditional to see if the user has connections. If not, I wanna show "You have no connections". I'm doing this by checking if (!companies.length) then show "you have no connections". companies is set as in empty array in the state. I don't understand why it's undefined. Even if the user has no connections, companies is still an empty array. so why why would companies.length return this error? How can I improve this code to avoid this problem?
function UserConnections() {
const { currentUser } = useContext(UserContext);
const connections = currentUser.connections;
const [companies, setCompanies] = useState([]);
useEffect(() => {
const comps = connections.map((c) => VolunteerApi.getCurrentCompany(c));
Promise.all(comps).then((comps => setCompanies(comps)));
}, [connections]);
if (!companies.length) {
return (
<div>
<p>You have no connections</p>
</div>
)
} else {
return (
<div>
{companies.map(c => (
<CompanyCard
key={c.companyHandle}
companyHandle={c.companyHandle}
companyName={c.companyName}
country={c.country}
numEmployees={c.numEmployees}
shortDescription={c.shortDescription}
/>
))}
</div>
);
}
};
Edit: Sorry, I should've included that the error is being thrown from a different component (UserLoginForm). This error is thrown when the user who has no connections logs in. But in the UserConnections component (code above), if I change if (!companies.length) to if (!companies), the user can login fine, but UserConnections will not render anything at all. That's why I was sure the error is refering to the companies.length in the UserConnections component.
The UserLoginForm component has been working fine whether the user has connections or not, so I don't think the error is coming from here.
UserLoginForm
function UserLoginForm({ loginUser }) {
const [formData, setFormData] = useState({
username: "",
password: "",
});
const [formErrors, setFormErrors] = useState([]);
const history = useHistory();
// Handle form submission
async function handleSubmit(evt) {
evt.preventDefault();
let results = await loginUser(formData);
if (results.success) {
history.push("/companies");
} else {
setFormErrors(results.errors);
}
}
// Handle change function
function handleChange(evt) {
const { name, value } = evt.target;
setFormData(d => ({ ...d, [name]: value }));
}
return (
<div>
<div>
<h1>User Login Form</h1>
<div>
<form onSubmit={handleSubmit}>
<div>
<input
name="username"
className="form-control"
placeholder="Username"
value={formData.username}
onChange={handleChange}
required
/>
</div>
<div>
<input
name="password"
className="form-control"
placeholder="Password"
type="password"
value={formData.password}
onChange={handleChange}
required
/>
</div>
{formErrors.length
? <Alert type="danger" message={formErrors} />
: null
}
<button className="btn btn-lg btn-primary my-3">
Submit
</button>
</form>
</div>
</div>
</div>
);
};
Edit 2: The solutions provided in this thread actually solved the problem. The error message kept persisting due to a different problem coming from another component.
Change this:
{companies.map(c => (...)}
to this:
{companies && companies.map(c => (...)}
and this:
if (!companies.length) {
to this:
if (!companies || companies.length === 0) {
This will then check for a nullish value, before running the map operation or checking length.
Even if the user has no connections, companies is still an empty array. so why why would companies.length return this error?
Your assumption that companies is an empty array is incorrect. Somehow it is set to undefined. You can either protect against this by doing
if (!companies || companies.length == 0) {
or by making sure companies is always set to an array.

My Firebase app doesn't store current user data locally

After logging into my Firebase application, the user logs in, but when it renews, the current user's information is deleted. return firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL).then(...) It's still the same for me using the code. My code is here:
class LogIn extends Component {
constructor(props) {
super(props);
this.app = !firebase.apps.length ? firebase.initializeApp(CONFIG) : firebase.app();
this.user = firebase.auth().currentUser;
}
render() {
function SignIn() {
return firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
.then(function() {
return firebase.auth().signInWithEmailAndPassword(document.getElementById("email").value, document.getElementById("password").value)
.then((user) => {
if (firebase.auth().currentUser) {
document.getElementById("x").innerHTML = firebase.auth().currentUser.email;
}
})
})
}
return (
<div>
Login<br /><br />
<div>
Username<br />
<input id="email" type="text" autoComplete="new-password" />
</div>
<div style={{ marginTop: 10 }}>
Password<br />
<input id="password" type="password" autoComplete="new-password" />
</div>
<input type="submit" onClick={SignIn}/>
</div>
);
}};
When you reload the page Firebase automatically tries to refresh the user's authentication state. But since this requires a call to the server it takes some time, and for that reason the user is initially set to null.
To pick up when Firebase has finished refreshing the user's authentication state, use an auth state listener as shown in the first sample in the documentation on getting the current user:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});

The best way to pass function as props in Reactjs

I have two components A and B. Component A has a restriction on button function based on conditions, I want Component B's button to make use of the restriction function in Component A
//Component A
state = {
disabled = true,
password='Admin'
}
authorize = (e) => {
var passcode = 1234
if (passcode == this.state.password) {
this.setState({ disabled: false })
} else {
alert("Access code is incorrect please enter the correct access code")
}
}
render() {
return (
<div>
<form>
<input
style={{ width: 200 }}
placeholder="Enter Admin Access code "
onChange={(e) => this.setState({ password: e.target.value })}
/>
<Button bsStyle='primary' onClick={(e) => this.authorize(e)}> Enter</Button>
</form>
// Button disabled waiting for authorize function to be called
<Button disabled={this.state.disabled} >I am disabled</Button>
</div>
);
}
Component B
render() {
return (
// want to be able to do something like
<Button disabled={this.props.disabled}>I am disabled too</Button>
);
}
I want to make use of the authorize function in Component B, don't really know how 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>

Resume prevented form submission in React

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));
}

Categories

Resources