React delay button event for 2 seconds - javascript

<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>

Related

When submitting a form in react the defined function to submit to in the parent component is not called

I am at my wits end understanding why my submit is not working. I literally am using the same code base over and over and with all other parts of my code this was working fine.
Now for the new code part, it simply does not work anymore. I try to simply submit some fields from a form and pass the values from the child component back to the parent component. This is my child component:
import React, { Component } from 'react';
import { Modal, Button, Form } from 'react-bootstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { icon } from '#fortawesome/fontawesome-svg-core/import.macro';
import '../../../components/Modal.css';
class Alert extends Component {
state = {
type: 'price'
}
setType(type) {
this.setState({
type: type
})
}
render() {
const { alert, handleClose, show, header } = this.props
const { type } = this.state
const showHideClassName = show ? "modal display-block" : "modal display-none";
return (
<div className={showHideClassName}>
<section className="modal-main">
<Form onSubmit={alert}>
<Modal.Dialog>
<Modal.Header className="modalTitle">
<Modal.Title>{ header }</Modal.Title><FontAwesomeIcon className="create-alert-icon" icon={icon({name: 'bell', style: 'solid' })} />
</Modal.Header>
<Modal.Body className="modalTitle" >
<Form.Group>
<label htmlFor="type">Type:</label>
<Form.Control
className="modalInput form-control form-control-sm"
name="type"
as="select"
value={type}
onChange={e => {
this.setType(e.target.value);
}}
>
<option value="price">Price</option>
<option value="amount">Amount</option>
</Form.Control>
</Form.Group>
{
alertType === 'price' ?
<span>
<label className="modalTitle" htmlFor="price">Alert me based on price:</label>
<Form.Control className="modalInput" name="amount" id="amount" size="sm" type="text" placeholder="1.22" />
</span>
: <span></span>
}
{
alertType === 'amount' ?
<span>
<label className="modalTitle" htmlFor="amount">Alert me based on amount:</label>
<Form.Control className="modalInput" name="amount" id="amount" size="sm" type="text" placeholder="300" />
</span>
: <span></span>
}
</Modal.Body>
<Modal.Footer>
<Button type="button" onClick={handleClose} variant="secondary">Close</Button>
<Button type="submit" onClick={handleClose} variant="primary">Submit</Button>
</Modal.Footer>
</Modal.Dialog>
</Form>
</section>
</div>
)
}
}
export default Alert;
This is my function in the parent component:
alert = (e) => {
e.preventDefault()
const { user } = this.props
const { amount, type } = e.target
const alert = { name: this.props.name, value: amount, type: type }
console.log(alert);
addAlert(alert);
}
This is my component in the parent that is being called and passes the props:
<CreateAlert show={ showCreateAlertModal } alert={this.alert} handleClose={ this.hideCreateAlertModal } header="Create a new Alert" />
When I submit using the Alert component, the modal is closed and nothing happens, nothing reaches the alert function, I have tried to move the component around in the parent, I have checked if the submit button is inside of the form (yes it is). I have tried to define the function one level above on another parent and pass the function through props two levels down to the child component, I have tried to remove content inside of the child component until there was just the submit button and the form left, and none of these things do any submit, the only thing that works is to define the button as submit directly which causes standard html5 submit to do its work, this works ... so I am really at a loss why react is not able to use the same boilerplate once more to do the same action it did a couple of times before ... I hope someone has an idea for me here, since I am getting mad at this, since everything looks fine ...
The problem is that when you click the Submit button, it will call handleClose and close the modal, and then there will be no Submit button to submit. What you have to do is in your same alert function add the handleSubmit() function.
alert = (e) => {
e.preventDefault()
const { user } = this.props
const { amount, type } = e.target
const alert = { name: this.props.name, value: amount, type: type }
console.log(alert);
addAlert(alert);
handleClose();
}
And leave the Submit button without any onClick handler, since the form itself will trigger the closing and the submitting logic.
I guess I found my issue .... it seems like it is not smart to put e.preventDefault() function inside of the handleClose() function which closes the modal ... guess the handleClose function fired first preventing the submit from working properly :/

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

Reactjs: problem converting <input type="file"> to custom button icon

I am new to React and trying to build an application where I want to press a custom button which will open a file dialog and upload a file on selecting it. Here is my code:
class ComposeButtons extends Component{
constructor(props) {
super(props);
this.state={
selectedFile: null
};
this.myInput = React.createRef();
}
fileSelectedHandler = (event) => {
console.log(event.target.files[0]);
this.setState({
selectedFile: event.target.files[0]
})
};
triggerClick = () => {
this.myInput.current.click()
};
fileUploadHandler = () => {
/* file upload triggered */
console.log('file upload triggered');
};
render() {
return(
<div className={"composeForm-area"}>
<div>
<Input
style={{display:'none'}}
type={"file"}
onChange={this.fileSelectedHandler}
ref={this.myInput}
/>
<Button onClick={this.triggerClick}
onChange={this.fileUploadHandler}
style={{ backgroundColor: '#DDDCDC'}}>
<Icon style={{ fontSize: '20px'}} type="camera" />
</Button>
</div>
</div>
)
}
}
export default ComposeButtons;
My current output:
I only get a clickable icon like above, however, upon clicking it throws:
Uncaught TypeError: _this.myInput.current.click is not a function
at eval (ComposeButtons.js:88)
at Button._this.handleClick (button.js:143)
at HTMLUnknownElement.callCallback (react-dom.development.js:14
What I want:
I simply want to open a file dialog to select file when i click this camera button and after i select and press ok in file dialog, it should close and trigger fileUploadHandler function printing the message on the console. That's all!
What I tried:
Apart from code above I tried to replace code inside div in render method with this code:
<div>
<Input
id="myInput"
style={{display:'none'}}
type={"file"}
onChange={this.fileSelectedHandler}
ref={(ref) => this.myInput = ref}
/>
<Button onClick={(e) => this.myInput.click() }
style={{ backgroundColor: '#DDDCDC'}}>
<Icon style={{ fontSize: '20px'}} type="camera" />
</Button>
</div>
I also followed almost all the answers on stackoverflow but nothing seems to working for me. It will be really helpful if someone can point me in right direction.
This is my first hobby project in React.
As far as I got your question. All we can do is add a label tag referring to the input type file using the for attribute in the label tag. By doing this we don't need to use ref
For info in this link.
Now all that needed to be done is to write appropriate css for the label tag
<div>
<label htmlFor="myInput"><Icon style={{ fontSize: '20px'}} type="camera" /></label>
<input
id="myInput"
style={{display:'none'}}
type={"file"}
onChange={this.fileSelectedHandler}
/>
</div>
After that, to trigger file upload. we can call fileUploadHandler after fileSelectedHandler is called.
fileSelectedHandler = (event) => {
console.log(event.target.files[0]);
this.setState({
selectedFile: event.target.files[0]
}, () => this.fileUploadHandler());
};
I would recommend using a library instead of building this yourself. Handle files can be tricky as soon as you want to do just a little more. Try out https://github.com/react-dropzone/react-dropzone. It works great and is simple to use.
If you are okay with using hooks there is a package that solves your problem.
https://www.npmjs.com/package/use-file-picker
import { useFilePicker } from 'use-file-picker';
function App() {
const [filesContent, errors, openFileSelector] = useFilePicker({
multiple: true,
accept: '.ics,.pdf',
});
if (errors.length > 0) return <p>Error!</p>;
return (
<div>
<button onClick={() => openFileSelector()}>Reopen file selector</button>
<pre>{JSON.stringify(filesContent)}</pre>
</div>
);
}
I created this hook for the same reason as you have.

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

Wrapping a react-router Link in an html button as a submit option

I'm trying to wrap a react-router Link in a submit button. I know there is this option: Wrapping a react-router link in an html button but it doesn't handle the submit itself.
I have something like this in mind:
<button type='submit'>
<Link to={{pathname : "/user/signup", search:"?page=sectionTwo"}} >
CONTINUE
</Link>
</button>
The Form itself is handled by Formik, if that helps.
I am assuming you have
<form onSubmit={someFunction}>
// Your submit button somewhere here
</form>
And you want to redirect user to a different page when the user clicks the submit button. I would approach it this way,
constructor(props) {
super(props);
this.state = { redirect: false }
}
handleSubmit() {
// do some check before setting redirect to true
this.setState({ redirect: true });
}
render() {
// you could reset the this.state.redirect to false here before redirecting a user
if (this.state.redirect) return <Redirect to='some url' />;
else return (
<div>
<form onSubmit={this.handleSubmit.bind(this)}>
<button type='submit'> Continue </button>
</form>
</div>
)
}
Idea is when the user clicks submit, it updates state, re-renders the component and see if redirect is true, if so it will redirect the user to the page. I think it is awkward to wrap Link that is not supposed to work as a button -IMO.
Create onSubmit handler and inside create a transition to another page:
onSubmit = (value) => {
const { history } = props;
history.push('your link');
}
And do not have to use it Link component

Categories

Resources