How to turn a handleSubmit form function into a reusable one - javascript

I'm using the React-Bootstrap handleSubmit function to validate a form , submit it , and at the same time call sign() to register my user.
Since I will be using a similar form to login the user (just changing the function that gets called inside), and probably in more views, I would like to "outsource" this function, turn it into a reusable one, put in a different file, and call it when needed, passing the arguments required , to make my code cleaner and less repetitive. But I am quite lost about where to start and how I should do it.
Any help is much appreciated.
In my SignUp.jsx component :
import { useState, useContext } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";
function SignUp() {
const [validated, setValidated] = useState(false);
const handleSubmit = (e) => {
const form = e.currentTarget;
console.log(form);
if (form.checkValidity() === false) {
e.preventDefault();
e.stopPropagation();
}
setValidated(true);
if (form.checkValidity() === true) {
e.preventDefault();
sign();
}
};
const sign = () => {
console.log("user signed up");
}
const handleChangeHandler = (e) => {
setNewUser({ ...newUser, [e.target.name]: e.target.value });
};
// I omitted many Form fields to reduce the code
return (
<div className="containerSignUp">
<div className="innerContainerSignUp">
<h1>Sign Up</h1>
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Row>
<Col>
<Form.Group className="mb-3" controlId="formBasicUserName">
<Form.Label>Username</Form.Label>
<Form.Control
required
name="userName"
value={newUser.userName ? newUser.userName : ""}
type="text"
onChange={handleChangeHandler}
/>
<Form.Control.Feedback type="invalid">
Please pick a user name.
</Form.Control.Feedback>
</Form.Group>
</Col>
</Row>
<Button type="submit" className="signButton">
Signup
</Button>
</Form>
</div>
</div>
);
}
export default SignUp;
My attempt :
I created a file validateForm.js :
const handleSubmit = (event, func) => {
// const [validated, setValidated] = useState(false); // I cannot use a state outside a react component.
const form = e.currentTarget;
console.log("form", form);
if (form.checkValidity() === false) {
e.preventDefault();
e.stopPropagation();
}
setValidated(true);
if (form.checkValidity() === true) {
e.preventDefault();
func();
}
};
export { handleSubmit };
In signUp.jsx I import it, and call it :
import { handleSubmit } from "../utils/validateForm";
And call it when I submit the form :
<Form noValidate validated={validated}
onSubmit={(e) => {
handleSubmit(e, sign, setValidated(true));}}>

You can achieve this using a custom hook:
useHandleSubmit.js
const useHandleSubmit() {
const [validated, setValidated] = useState(false);
const handleSubmit = (event, func) => {
const form = event.currentTarget;
console.log("form", form);
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
// maybe you must move this inside the next `if`, otherwise move it to the end
setValidated(true);
if (form.checkValidity() === true) {
event.preventDefault();
func();
}
};
return { handleSubmit, validated };
}
export default useHandleSubmit;
In signUp.jsx:
import useHandleSubmit from '../utils/useHandleSubmit';
...
const { handleSubmit, validated } = useHandleSubmit();
...
<Form noValidate validated={validated}
onSubmit={(e) => {
handleSubmit(e, sign}}>

Related

How to have a MUI TextField have an onChange and an onSubmit event?

I am using react and mui. I have a form like so:
const handleChange = (event) => {
set_user_input(event)
}
const handleSubmit = () => {
if (user_input == 'help'){
console.log('help')
}
else{
console.log('no help')
}
}
<form onSubmit={handleSubmit()}>
<label>
guest#website.com:: {'~ >>'}
</label>
<TextField
id="outlined-name"
value={user_input}
onChange={e => handleChange(e.target.value)}
/>
</form>
The idea is to update state of a variable with the onChange method, and have the handleSubmit method fire off when the form is submitted.
As an aside, how do I stop the form from reloading the page when it is submitted? Thanks!
There could be other issues but just for the posted code it seems that handleSubmit should call e.preventDefault() to prevent the page reload:
const handleChange = (e) => {
set_user_input(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (user_input === "help") {
console.log("help");
} else {
console.log("no help");
}
};
In the output handleSubmit can be assigned to onSubmit but not to run it on site:
<form onSubmit={handleSubmit}>
<label>guest#website.com:: {"~ >>"}</label>
<TextField id="outlined-name" value={user_input} onChange={handleChange} />
</form>

How can I access key using value in ReactJs?

I'm following a reactJS tutorial from Udemy and I was trying something on my own.
Project:
This is a simple project where I can add or remove goals
What I want to add extra is I want to clear the input text field when clicked on submit(Add goal) button. And I am able to do it but I think there is one problem.
The Whole Code
import React, { useState } from "react";
import Button from "../../UI/Button/Button";
import style from "./CourseInput.module.css";
const CourseInput = (props) => {
const [enteredValue, setEnteredValue] = useState("");
const [isValid, setIsValid] = useState(true);
const goalInputChangeHandler = (event) => {
setEnteredValue(event.target.value);
if (enteredValue.trim().length > 0) {
setIsValid(true);
}
console.log("=>" + enteredValue);
};
const formSubmitHandler = (event) => {
event.preventDefault();
if (enteredValue.trim().length === 0) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
// empty the inputbar and reset state (enteredValue)
console.log(event);
event.target[0].value = ""; // the main
setEnteredValue(""); // two lines.
};
return (
<form onSubmit={formSubmitHandler}>
<div className={`${style["form-control"]} ${!isValid && style.invalid}`}>
<label>Course Goal</label>
<input type="text" onChange={goalInputChangeHandler} />
</div>
<Button type="submit">Add Goal</Button>
</form>
);
};
export default CourseInput;
as you can see in above code I'm accessing input field through form events.
I accessed the input field through index number which I think is hard coded. In future if number of form element increases/decreases there is a chance that index number might change. so what I want to do is access the input value without using index. Is that possible?(I know it is), and how do I do it?
If you are working with a form's onSubmit action and have the onSubmit event then you can reset the form directly.
const formSubmitHandler = (event) => {
event.preventDefault();
if (enteredValue.trim().length === 0) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
setEnteredValue("");
event.target.reset(); // <-- calls form's reset action
};
const CourseInput = (props) => {
const [enteredValue, setEnteredValue] = React.useState("");
const [isValid, setIsValid] = React.useState(true);
const goalInputChangeHandler = (event) => {
setEnteredValue(event.target.value);
if (enteredValue.trim().length > 0) {
setIsValid(true);
}
};
const formSubmitHandler = (event) => {
event.preventDefault();
if (!enteredValue.trim().length) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
setEnteredValue("");
event.target.reset();
};
return (
<form onSubmit={formSubmitHandler}>
<div>
<label>Course Goal</label>
<input type="text" onChange={goalInputChangeHandler} />
</div>
<button type="submit">Add Goal</button>
</form>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(
<CourseInput onAddGoal={(val) => console.log(val)} />,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />
You may use Refs in order to achieve this:
function MyComponent() {
const textInput = React.useRef(null);
const formSubmitHandler = (event) => {
event.preventDefault();
if (enteredValue.trim().length === 0) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
// empty the inputbar and reset state (enteredValue)
console.log(event);
textInput.current.value = "";
};
// other code
return <input type="text" onChange= {goalInputChangeHandler} ref={textInput} />
}
It's enough to add value={enteredValue} to input field and set it to empty string after submit the form.
Also if you have any other input field, you can try event.target.reset().
So try this:
import React, { useState } from "react";
// import styled from "styled-components";
import Button from "../../UI/Button/Button";
import style from "./CourseInput.module.css";
const CourseInput = (props) => {
const [enteredValue, setEnteredValue] = useState("");
const [isValid, setIsValid] = useState(true);
const goalInputChangeHandler = (event) => {
setEnteredValue(event.target.value);
if (enteredValue.trim().length > 0) {
setIsValid(true);
}
console.log("=>" + enteredValue);
};
const formSubmitHandler = (event) => {
event.preventDefault();
if (enteredValue.trim().length === 0) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
// empty the inputbar and reset state (enteredValue)
setEnteredValue("");
event.target.reset();
};
return (
<form onSubmit={formSubmitHandler}>
<div className={`${style["form-control"]} ${!isValid && style.invalid}`}>
<label>Course Goal</label>
<input type="text" value={enteredValue} onChange={goalInputChangeHandler} />
</div>
<Button type="submit">Add Goal</Button>
</form>
);
};
export default CourseInput;

React submit form button won't work without clicking more than once

I am working on my first React program, it is the one provided by Sololearn (Contact Manager). I am trying to add a function to search for a contact: SearchName.
However, I need to click many times on the Search button for it to work. Can someone please tell me where I went wrong?
For example, typing James Smith in the enter a name to search field first gives "is not in list". Then when clicked again, it updates to is in list.
Here is the code:
import React, { useState } from "react";
import ReactDOM from "react-dom";
function AddPersonForm(props) {
const [person, setPerson] = useState("");
function handleChange(e) {
setPerson(e.target.value);
}
function handleSubmit(e) {
if (person !== "") {
props.handleSubmit(person);
setPerson("");
}
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add new contact"
onChange={handleChange}
value={person}
/>
<button type="submit">Add</button>
</form>
);
}
function RemovePersonForm(props) {
const [person, setPerson] = useState("");
function handleChange(e) {
setPerson(e.target.value);
}
function handleSubmit(e) {
props.handleSubmit(person);
setPerson("");
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="enter name to delete"
onChange={handleChange}
/>
<button type="submit">Delete</button>
</form>
);
}
function PeopleList(props) {
const arr = props.data;
const listItems = arr.map((val, index) => <li key={index}>{val}</li>);
return <ul>{listItems}</ul>;
}
function SearchName(props) {
const [contacts, setContacts] = useState(props.data);
const [person, setPerson] = useState("");
const [isInList, setIsInList] = useState(false);
const [text, setText] = useState("");
function handleChange(e) {
setPerson(e.target.value);
}
function handleSubmit(e) {
setIsInList(false);
for (var c of contacts) {
if (c == person) {
setIsInList(true);
break;
}
}
if (isInList) {
setText("is in list");
} else {
setText("is not in list");
}
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="enter name to search"
onChange={handleChange}
/>
<button type="sumbit">Search</button>
<p>{text}</p>
</form>
);
}
function ContactManager(props) {
const [contacts, setContacts] = useState(props.data);
function addPerson(name) {
setContacts([...contacts, name]);
}
function removePerson(name) {
var newContacts = new Array();
var i = 0;
for (var c of contacts) {
if (c != name) {
newContacts[i] = c;
i++;
}
}
setContacts(newContacts);
}
return (
<div>
<AddPersonForm handleSubmit={addPerson} />
<RemovePersonForm handleSubmit={removePerson} />
<SearchName data={contacts} />
<PeopleList data={contacts} />
</div>
);
}
const contacts = ["James Smith", "Thomas Anderson", "Bruce Wayne"];
ReactDOM.render(
<ContactManager data={contacts} />,
document.getElementById("root"),
);
The root issue is that since setState is asynchronous, isInList hasn't had the time to change by the time you're checking it in handleSubmit.
However, since the printed text is strictly a function of whether isInList is true, it shouldn't be a separate state atom. If the computation was more complex, I'd recommend using useMemo for it.
On a similar note, you shouldn't "fork" the data prop to local contacts state.
Finally, you can simplify the finding procedure to a simple .find call instead of a loop.
function SearchName(props) {
const [person, setPerson] = useState("");
const [isInList, setIsInList] = useState(false);
function handleChange(e) {
setPerson(e.target.value);
}
function handleSubmit(e) {
setIsInList(props.data.find((c) => c === person));
e.preventDefault();
}
const text = isInList ? "is in list" : "is not in list";
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="enter name to search"
onChange={handleChange}
/>
<button type="sumbit">Search</button>
<p>{text}</p>
</form>
);
}
The problem is here
setIsInList(true);
This is an async method, meaning it will be updated after the whole function is executed. To solve this you can use useMemo method as follows (as expalained by #AKX. useEffect shouldnt be used)
const searchResult = useMemo(() => {
if (isInList) {
return "is in list";
} else {
return "is not in list";
}
}, [isInList]);
CodeSandbox here
It's because React states only update after an eventHandler is finished - that's something called batch update, In your case, you need to go "another round" to have it updated.
Here is my suggestion from your code:
Your modified sourcecode
Keep up the good learning!

If event is Enter Button or mouse click, do something

I have a search bar and I want the user to be able to call the handleSearch function when they click the search button or hit the enter key. The search button works fine but I can't for the life of me get it to work when the enter key is hit. Help, please :)
import React, { useState } from 'react';
import { Form, FormControl, Button } from 'react-bootstrap';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { searchBlogs, setSearchQuery } from '../../actions/searchBlogsAction';
function SearchComponent() {
const history = useHistory();
const dispatch = useDispatch();
const [searchInput, setSearchInput] = useState('');
const inputHandler = (e) => {
setSearchInput(e.target.value);
};
// Search blogs and redirect to the search results page
const handleSearch = (e) => {
if (e.keyCode === 13 || e == ??) {
e.preventDefault();
history.push('/search-results');
dispatch(setSearchQuery(searchInput));
dispatch(searchBlogs(searchInput));
setSearchInput('');
}
};
return (
<Form inline>
<FormControl
type="text"
size="sm"
placeholder="Search"
className="mr-sm-2"
onChange={inputHandler}
value={searchInput}
onKeyPress={handleSearch}
/>
<Button size="sm" variant="outline-secondary" onClick={handleSearch}>
Search
</Button>
</Form>
);
}
export default SearchComponent;
Try this:
<FormControl
type="text"
size="sm"
placeholder="Search"
className="mr-sm-2"
onChange={inputHandler}
value={searchInput}
onKeyPress={event => event.key === "Enter" && handleSearch()}
/>
And the handleSearch function should just be:
const handleSearch = (e) => {
e.preventDefault();
history.push('/search-results');
dispatch(setSearchQuery(searchInput));
dispatch(searchBlogs(searchInput));
setSearchInput('');
}
According to react-bootstrap docs regarding Form, you can pass onSubmit callback to the Form component, like so:
- <Form inline>
+ <Form inline onSubmit={handleSearch}>
You want to add an onSubmit event handler to the form instead.
import React, { useState } from 'react';
import { Form, FormControl, Button } from 'react-bootstrap';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { searchBlogs, setSearchQuery } from '../../actions/searchBlogsAction';
function SearchComponent() {
const history = useHistory();
const dispatch = useDispatch();
const [searchInput, setSearchInput] = useState('');
const inputHandler = (e) => {
setSearchInput(e.target.value);
};
// Search blogs and redirect to the search results page
const handleSearch = (e) => {
e.preventDefault();
history.push('/search-results');
dispatch(setSearchQuery(searchInput));
dispatch(searchBlogs(searchInput));
setSearchInput('');
};
return (
<Form inline onSubmit={handleSearch}>
<FormControl
type="text"
size="sm"
placeholder="Search"
className="mr-sm-2"
onChange={inputHandler}
value={searchInput}
/>
<Button type="submit" size="sm" variant="outline-secondary">
Search
</Button>
</Form>
);
}
export default SearchComponent;
Clicking the submit button in a form will trigger the submit event as will hitting the enter key.

Is it possible to use jquery style when adding attributes in react js?

I have html form taken from api as string, i need to add onSubmit event on it, like in jquery style, Is it possible and how to do it?
import React from 'react';
import ReactHtmlParser from 'react-html-parser';
class MyForm extends React.Component {
const htmlForm = "<form><input type='email' name='user[email]' /></form>"; // this taken from API server as string
const reactForm = ReactHtmlParser(htmlForm);
// i want to do like this
reactForm.on('submit', (e) => {
e.preventDefault();
console.log('submitted')
})
}
But it doess not work, any ideas?
Another idea in pure javascript, but doesn't work yet. There is a problem on converting pure html + javascript to react component. I don't know yet what best library to convert it.
import React from 'react';
import axios from 'axios';
import ReactHtmlParser from 'react-html-parser';
class NewChatButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick = (e) => {
e.preventDefault();
this.props.newChatButtonCallback({modal: {body: 'loading...'}});
axios.get('/users/rooms/new.json')
.then(response => {
const reactForm = ReactHtmlParser(response.data);
const html = new DOMParser().parseFromString(response.data, 'text/html').body;
const form = html.querySelector('form');
const select = form.querySelector('select');
form.addEventListener('submit', (e) => {
e.preventDefault();
console.log('submitted');
});
select.addEventListener('change', (e) => {
e.preventDefault();
console.log('changed')
})
this.props.newChatButtonCallback({
modal: {
title: 'Cari Tutor',
body: reactForm
}
});
}).catch(error => console.log(error));
}
render () {
return (
<React.Fragment>
<button onClick={this.handleClick} className="btn btn-link" data-toggle="modal" data-target="#mainModal">
<i className="material-icons">add</i>
</button>
</React.Fragment>
)
}
}
export default NewChatButton;
You can use transform from the react-html-parser:
transform = (node, index) => {
if (node.type === 'tag' && node.name === 'form') {
return (
<form
key={index}
{...node.attribs}
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
console.debug('submitted')
}}
>
{node.children.map((child, i) => {
return <child.name key={i} {...child.attribs}></child.name>
})}
<button type="submit">Submit</button>
</form>
)
}
}
And here is how you can render:
render() {
const htmlForm = "<form><input type='email' name='user[email]' /></form>"
const reactForm = ReactHtmlParser(htmlForm, { transform: this.transform })
return (
<>
<div>{reactForm}</div>
</>
)
}
Also, you don't need a submit button in a single text input form on most browsers, pressing Enter in the field submits it. But it's best to have one. T.J. Crowder's comment
I have added the Submit button. You can hide it with CSS if you don't want to show it.
Live copy on CodeSandbox

Categories