Trigger Validation on Specific Row using React and Formik - reactjs

I have two "save" buttons which basically do the same thing.
I wanted the other one onAnotherSubmit which has a type="button" to trigger the validation.
How do i do it?
CODESANDBOX
const anotherSubmit = ({ values, errors, validateForm }) => {
validateForm().then((validation) => formik.setTouched(validation));
console.log(errors);
console.log(values);
if (!errors) {
console.log("yes submit");
}
};
<Button
variant="contained"
color="primary"
type="button"
onClick={() => anotherSubmit(formik)}
>
Save with type="button"
</Button>

Related

How should I structure a React Form with multiple options for onSubmit behaviors? [duplicate]

I want to have two submit buttons for my form.
Both will use the same inputs and form validation, but will do different tasks
export default function Formtwosubmits() {
function handlesubmit_task1(){
}
function handlesubmit_task2(){
}
return (
<>
<form onSubmit={handlesubmit_task1 if task 1 button pressed || handlesubmit_task2 if task2 button pressed } >
<Button variant="contained" color="primary" type="submit">
task 1
</Button>
<Button variant="contained" color="primary" type="submit">
task 2
</Button>
.... here input fields
</form>
</>
);
}
I am not able to undertand how to pass different handlesumit functions for different buttons
I have tried:
export default function Formtwosubmits() {
function handlesubmit_task1(){
}
function handlesubmit_task2(){
}
return (
<>
<form >
<Button variant="contained" color="primary" type="submit" onClick={handlesubmit_task1}>
task 1
</Button>
<Button variant="contained" color="primary" type="submit" onClick={handlesubmit_task2}>
task 2
</Button>
.... here input fields
</form>
</>
);
}
putting onClick on the button. with type=submit but this does not check the form validation (like required etc), whereas with onSubmit i see the form validation is checked
How can i trigger form validation with onClick. even that can help me
Use a React ref on the form element to access the form fields in the submit handlers and attach the submit handlers to each button's onClick handler.
function Formtwosubmits() {
const formRef = useRef(); // (1) <-- React ref for form DOMNode
function handlesubmit_task1(event) {
event.preventDefault();
const { value } = formRef.current.myInput; // (4) <-- access form inputs by name
// ...anything you need to do with form fields
console.log("handler 1", value);
formRef.current.reset(); // (5) <-- reset form if necessary
}
function handlesubmit_task2(event) {
event.preventDefault();
const { value } = formRef.current.myInput;
console.log("handler 2", value);
formRef.current.reset();
}
return (
<>
<form ref={formRef}> // (2) <-- Attach ref to element
<button
variant="contained"
color="primary"
type="submit"
onClick={handlesubmit_task1} // (3) <-- Attach submit handler 1
>
task 1
</button>
<button
variant="contained"
color="primary"
type="submit"
onClick={handlesubmit_task2} // (3) <-- Attach submit handler 2
>
task 2
</button>
.... here input fields
<input name="myInput" type="text" />
</form>
</>
);
}
Demo
Update
You can assign an id to each submit button and access the React Synthetic event's nativeEvent to get to the underlying browser event and access the submitter value.
Create a submitHandler to receive the form's onSubmit event and check the submitter value and proxy the onSubmit event to the proper handler based on id.
const handlers = {
submit1: handlesubmit_task1,
submit2: handlesubmit_task2,
}
const submitHandler = (e) => {
const { id } = e.nativeEvent.submitter; // <-- access submitter id
handlers[id](e); // <--proxy event to proper callback handler
};
function handlesubmit_task1(event) {
event.preventDefault();
const { value } = event.target.myInput;
console.log("handler 1", value);
event.target.reset();
}
function handlesubmit_task2(event) {
event.preventDefault();
const { value } = event.target.myInput;
console.log("handler 2", value);
event.target.reset();
}
<form onSubmit={submitHandler}>
<button
id="submit1" // <-- id 1
variant="contained"
color="primary"
type="submit"
>
task 1
</button>
<button
id="submit2" // <-- id 2
variant="contained"
color="primary"
type="submit"
>
task 2
</button>
.... here input fields
<input name="myInput" type="text" />
</form>
You could write it like this:
export default function App() {
function handleSubmitTask1() {
console.log("Execute Task1");
}
function handleSubmitTask2() {
console.log("Execute Task2");
}
return (
<form
onSubmit={(e) => {
const buttonName = e.nativeEvent.submitter.name;
if (buttonName === "button1") handleSubmitTask1();
if (buttonName === "button2") handleSubmitTask2();
}}
>
<input name="123" />
<button type="submit" name="button1">
task 1
</button>
<button type="submit" name="button2">
task 2
</button>
</form>
);
}
The main point is to get the button's name which triggers the form's submission.
Use onClick on the button themselves and add type='submit' to submit it.
export default function Formtwosubmits() {
function handlesubmit_task1(e){
}
function handlesubmit_task2(e){
}
return (
<>
<form >
<Button variant="contained" color="primary" type="submit" onClick={handlesubmit_task1}>
task 1
</Button>
<Button variant="contained" color="primary" type="submit" onClick={handlesubmit_task2}>
task 2
</Button>
.... here input fields
</form>
</>
);
}
This worked for me, using event.nativeEvent.submitter.id:
Form (note the id property value is distinct for each button):
<form onSubmit={this.testHandle}>
<input type="submit" id="btn1" value="Submit button 1" />
<input type="submit" id="btn2" value="Submit button 2" />
</form>
Handler:
testHandle(event) {
console.log(event.nativeEvent.submitter.id);
event.preventDefault();
}
It successfully prints in the console btn1 or btn2, depending on which button of the Form you click.
Note: using React 18.2.0.

Disable button after click in React

I have this piece of code but can't work out how I can disable the button after Pay is clicked:
} else if (paymentMethod.name === "Pay by Cash") {
return (
<Tab.Pane eventKey={paymentMethod.id} key={key}>
<h6 className="mb-3 mt-0 mb-3">Cash</h6>
{this.state.paymentMethodSelectionError && this.state.paymentMethodSelectionError !== "" && <Alert key="error_div" variant="danger">{this.state.paymentMethodSelectionError}</Alert>}
<p>You are paying by cash</p>
<Form.Group className="mb-0">
<Button className="btn btn-success btn-block btn-lg"
onClick={() => {
this.setState({
selectedPaymentMethod: "not-charged",
paymentGateway: paymentMethod.id
}, () => {
this.setPaymentMethod()
})
}}>
PAY £{this.state.totalPay}<Icofont icon="long-arrow-right" /></Button>
</Form.Group>
</Tab.Pane>)
Any help is appreciated
You can put a state for disabling your button:
this.state = {
disabled: false
}
In click function, change it to true:
const clickButton = () => {
this.setState({ disabled: true });
}
Then change your HTML to something like this:
<button type="button" disabled={ this.state.disabled }>Click Me!</button>
Preview:
Here
Declare a new state variable to take care of the disabled button:
const [buttonState,setButtonState]=useState(false)
Attach a onClick handler to disable the button and also manage the disabled property of the button.
<button onClick={()=>{ setButtonState(true)}} disabled={buttonState}> PAY </button>
You can use state to control the button state
Preview for both Functional and Class components
Codesandbox Preview Link

Unable to set `isSubmitting` with Formik

Edit
As it turns out, it was working all along -- the issue was because my handleLogin method was async
New sandbox:
I have a basic Form component. It passes setSubmitting as one of the available methods, and it passes isSubmitting as well. I want to disable the submit button while the form is submitting, but I'm having trouble with this.
Initially, I had a <form> element and I was trying to set setSubmitting(true) in the below part:
<form
onSubmit={(credentials) => {
setSubmitting(true); // <--
handleSubmit(credentials);
}}
>
But this didn't work. So I've tried getting rid of the <form> and changing <Button> to type="button" instead of submit, and I did,
<Button
color="primary"
disabled={isSubmitting}
fullWidth
size="large"
type="button"
variant="contained"
onClick={() => {
setSubmitting(true);
handleLogin(values);
}}
>
Submit
</Button>
But the problem with this, is that in order to do setSubmitting(false) in case of an error is that I have to do this,
onClick={() => {
setSubmitting(true);
handleLogin(values, setSubmitting); // <--
}}
And in addition to this, I have no use for onSubmit={handleLogin}, but if I remove that, Typescript complains.
There's got to be an easier way to accomplish this (without using useFormik).
What can I do here?
Here is the component:
import * as React from "react";
import { Formik } from "formik";
import { Box, Button, TextField } from "#material-ui/core";
const Form = React.memo(() => {
const handleLogin = React.useCallback(async (credentials, setSubmitting) => {
console.log(credentials);
setTimeout(() => {
setSubmitting(false);
}, 2000);
}, []);
return (
<Formik
initialValues={{
email: ""
}}
onSubmit={handleLogin} // removing this line make Typescript complain
>
{({
handleSubmit,
handleChange,
setSubmitting,
isSubmitting,
values
}) => (
<div>
<TextField
fullWidth
label="Email"
margin="normal"
name="email"
onChange={handleChange}
value={values.email}
variant="outlined"
/>
<Box sx={{ my: 2 }}>
<Button
color="primary"
disabled={isSubmitting}
fullWidth
size="large"
type="button"
variant="contained"
onClick={() => {
setSubmitting(true);
handleLogin(values, setSubmitting);
}}
>
Submit
</Button>
</Box>
</div>
)}
</Formik>
);
});
export default Form;
You forget to put the form inside your Formik component
<Formik>
{...}
<form onSubmit={handleSubmit}>
{...}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
</Formik>
so now you can use your button as submit.
demo: https://stackblitz.com/edit/react-egp1gc?file=src%2FForm.js

Update useState onclick not updating

I am trying to update a useState object, my goal is to toggle the buttons and keep which button is on and which is off in the useState.
const SalesChartCard = (data) => {
const [toggleButton, toggleButtons] = useState({
'impressions': true,
'purchasestart': true,
'purchasecancel': true,
'purchasecomplete': true
});
const buttonClick = (dimension) => {
toggleButtons({
dimension: !toggleButton.dimension
});
};
return (
<>
<Button
outline={toggleButton.impressions}
onClick={e => buttonClick('impressions')}
color="primary" className="mb-2">
Impressions
</Button>
<Button
outline={toggleButton.purchasestart}
onClick={e => buttonClick('purchasestart')}
color="secondary" className="mb-2">
Purchase Start
</Button>
<Button
outline={toggleButton.purchasecancel}
onClick={e => buttonClick('purchasecancel')}
color="danger" className="mb-2">
Purchase Cancel
</Button>
<Button
outline={toggleButton.purchasecomplete}
onClick={e => buttonClick('purchasecomplete')}
color="success" className="mb-2">
Purchase Complete
</Button>
</>
);
};
export default SalesChartCard;
The useState react hook doesn't shallow merge state updates, you need to manage this yourself.
For the given click handler: onClick={e => buttonClick('impressions')}
The buttonClick callback handler should merge existing state and use dynamic (computed property name) keys for updating the correct property. Use a functional state update to correctly compute the next state from the previous state. This ensures all previous state updates are correctly merged when updated.
const buttonClick = (dimension) => {
toggleButtons(prevState => ({
...prevState,
[dimension]: !prevState[dimension],
}));
};
Your state is an object. When you update an object you need to keep the previous value as well. So instead of creating a new object with your dimension key you have to use object spread to keep the previous state.
Besides that as your key is in a variable you need to use key inside [].
You can do something like this,
const SalesChartCard = (data) => {
const [toggleButton, toggleButtons] = useState({
'impressions': true,
'purchasestart': true,
'purchasecancel': true,
'purchasecomplete': true
});
const buttonClick = (dimension) => {
toggleButtons({
...toggleButton,
[dimension]: !toggleButton[dimension]
});
};
return (
<>
<Button
outline={toggleButton.impressions}
onClick={e => buttonClick('impressions')}
color="primary" className="mb-2">
Impressions
</Button>
<Button
outline={toggleButton.purchasestart}
onClick={e => buttonClick('purchasestart')}
color="secondary" className="mb-2">
Purchase Start
</Button>
<Button
outline={toggleButton.purchasecancel}
onClick={e => buttonClick('purchasecancel')}
color="danger" className="mb-2">
Purchase Cancel
</Button>
<Button
outline={toggleButton.purchasecomplete}
onClick={e => buttonClick('purchasecomplete')}
color="success" className="mb-2">
Purchase Complete
</Button>
</>
);
};
export default SalesChartCard;

my form not submitting so where is the problem

as you see my form not submit i can't see where is the problem i search but all the solution i found i already made it so what else can be the problem when i try to use console it don't show like it doesn't enter the submit function at all
this is the pieces in my component that work with form
const [editMyText, setEditMyText] = useState({
editIsActive: false,
textValue: body,
});
const inputRef = useRef(null);
const handleSaveEdit = (e) => {
setEditMyText({
editIsActive: false,
textValue: inputRef.current.value,
});
// updatePost(_id, textValue);
};
const handleOnSubmit = (e) => {
e.preventDefault();
console.log('done 1');
updatePost(_id, textValue);
console.log('done 2');
};
{body !== null && !editIsActive ? (
<p
className='card-text'
onClick={(e) => auth.user._id === user && handleEditText(e)}
>
{textValue}
</p>
) : (
<form
className='edit-post-form'
onSubmit={(e) => handleOnSubmit(e)}
>
<textarea
className='edit-text'
defaultValue={textValue}
ref={inputRef}
/>
<button
className='btn btn-raised btn-danger mr-2'
onClick={(e) => handleCloseEdit(e)}
>
Close
</button>
<button
type='submit'
className='btn btn-raised btn-success'
onClick={(e) => handleSaveEdit(e)}
>
Save
</button>
</form>
)}

Resources