how to handle these multiple forms? - javascript

the requirement is a bit tricky. dunno how to explain it but I'll try.
so I've 20 forms on my web page. and there's only one submit button that'll bulk-submit all the forms. but filling out all forms isn't required. the user will fill up as many forms as they like. but whichever form they fill up, must be fully filled. means all inputs are required on each form.
so I need to write a logic that'll make all the inputs required in a form if any of the input is filled there. I know I can use the onChange function and write a logic to make all the input required in one form. but I also need to remove the required from the inputs, if all the input field is again cleared. and I think removing the required from the inputs is the main complexity here. Because while adding required i can simply check if any of the inputs have value in it (using onChange on every input). but if I do the same for removing required, I can't be assured if one input field is cleared or all the inputs are cleared from that form. so in simpler words, I need to sync all the inputs on each form.
[NOTE-1: I'm in a React environment, so I've all the facilities of states and stuff]
[NOTE-2: I've made a react component for the form and looped over it 20 times. so you can think that as one form]
[NOTE-3: This is one of my client's projects. so I can't have changes to the requirements]

This is a pretty "business specific" problem, but I would tackle it along these lines. You may need to make adjustments to fit your exact requirements, but the general gist is there.
The key is to treat the "required" flag for each input as "derived" or calculated state. You said "but I also need to remove the required from the inputs" - I don't think that's entirely true, or doesn't fit the react model. You just need to check if other fields are populated in the current form in the current render.
const { useState } = React;
const { render } = ReactDOM;
const forms = [
{
inputs: ["field1", "field2"]
},
{
inputs: ["field3", "field4"]
}
];
function MegaForm() {
const [values, setValues] = useState(() => {
const values = {};
forms.forEach((form) => {
form.inputs.forEach((input) => {
values[input] = "";
});
});
return values;
});
const submit = () => {
console.log(values);
};
const isRequired = (formIndex) => {
return forms[formIndex].inputs.find(
(inputName) => values[inputName] !== ""
);
};
return (
<div>
{forms.map((form, i) => (
<form key={i}>
<h2>Form {i}</h2>
{form.inputs.map((input, j) => (
<div key={j}>
<label>
{input}
<input
value={values[input]}
onChange={(e) =>
setValues({ ...values, [input]: e.target.value })
}
required={isRequired(i)}
/>
{isRequired(i) ? "*" : ""}
</label>
</div>
))}
</form>
))}
<br />
<br />
<button type="button" onClick={submit}>
Submit
</button>
</div>
);
}
render(<MegaForm />, document.getElementById("app"));
CodePen: https://codepen.io/chrisk7777/pen/RwYWWqV?editors=0010

If you have all the forms with the same fields you could go with a solution like this:
export function FormsContainer() {
const [formData, setFormData] = React.useState({});
function onChangeGenerator(i: number) {
return (e) => {
setFormData((data) => ({
...data,
[i]: {
...data[i],
[e.target.name]: e.target.value,
},
}));
};
}
function fieldHasValue(value) {
return value !== null && value !== undefined && value !== '';
}
function formHasValidFields(i) {
return (
formData[i] &&
Object.keys(formData[i]).some((key) => fieldHasValue(formData[i][key]))
);
}
function submit() {
const result = Object.keys(formData).reduce((acc, i) => {
if (formHasValidFields(i)) {
acc.push(formData[i]);
}
return acc;
}, []);
console.log(result);
}
return (
<form
onSubmit={(e) => {
e.preventDefault();
submit();
}}
>
{[0, 1, 2, 3, 4, 5].map((i) => (
<SingleForm
key={i}
onChange={onChangeGenerator(i)}
required={formHasValidFields(i)}
/>
))}
<br />
<br />
<button type="submit">Submit</button>
</form>
);
}
function SingleForm({
required,
onChange,
}: {
required: boolean;
onChange: (e) => void;
}) {
return (
<React.Fragment>
<hr />
<input name="prop1" onChange={onChange} required={required} />
<input name="prop2" onChange={onChange} required={required} />
</React.Fragment>
);
}
StackBlitz: https://stackblitz.com/edit/react-ts-utrgbj

Related

Reset controlled value of single individual input in form upon button click

My app gets initialized with a json payload of 'original values' for attributes in a task form. There is a shared state between various components through a context manager submissionState that also gets imported to this form component. This shared state is a copy of the original values json but includes any edits made to attributes in the payload. I would like to include individual 'reset' buttons for each input of the form which would update the shared state to the original value in the json payload. The original values get passed into the parent form component as props and the edited value state gets called from within the parent component as well.
const FormInput = ({ fieldName, fieldValue, onChange, originalValue }) => {
const handleReset = e => {
//????
}
return (
<div>
<label htmlFor={fieldName}>
{fieldName}
</label>
<br />
<input type="text"
name={fieldName}
value={fieldValue}
onChange={onChange}/>
<button id="reset" onClick={handleReset}>↻</button>
<br />
<div>{originalValue}</div>
</div>
);
};
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
function handleChange(evt) {
const value = evt.target.value;
setSubmission({
...submissionState,
[evt.target.name]: value,
});
}
const taskFields = ["name", "address", "address_extended", "postcode", "locality", "region", "website"];
return (
<div>
<form>
{taskFields.map((field) => {
<FormInput
key={field}
fieldName={field}
fieldValue={submissionState[field]}
onChange={handleChange}
originalValue={payload[field]}
/>
})
}
</form>
</div>
);
};
export default TaskForm;
What I would like to do is include logic in the reset button function so that any edits which were made in a form input (from state) get reverted to the original value (stateless), which comes from the payload props: payload[field].
The form input is controlled through a global shared state submissionState, so the reset button logic can either modify the shared state itself with something like:
const handleReset = (submissionState,setSubmissionState) => {
setSubmission({
...submissionState,
fieldName: originalValue,
});
but I would need to pass the submissionState and setSubmission down through to the child component. It would be better if I can somehow update the value attribute in the input, which in-turn should potentially update the shared state? And the logic can just be something like this (assuming I can somehow access the input's value state in the reset button)
const handleReset = (?) => {
/*psuedo code:
setInputValueState(originalValue)
*/
}
I would highly recommend using react-hook-form if it's an option. I've implemented it across several projects and it has never let me down. If it's just not possible to use a library, then keep in mind that React is usually unidirectional. Don't try to work around it, since it works that way by design for most cases you can encounter. Otherwise…
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
const upsertSubmission = (upsert) =>
setSubmission({
...submissionState,
...upsert,
});
const handleChange = ({ target }) => {
upsertSubmission({
[target.name]: target.value,
});
};
const reset =
(originalValue) =>
({ target }) => {
upsertSubmission({
[target.name]: originalValue,
});
};
/* Also something like this. RHF will handle most of this for you!
* const reset = (originalValue, fieldName) =>
* upsertSubmission({[fieldName]: originalValue})
*/
const taskFields = [];
return (
<div>
<form>
{taskFields.map((field) => (
<FormInput
key={field}
fieldName={field}
onChange={handleChange}
reset={reset(originalValue)}
value={submissionState[field]}
/>
))}
</form>
</div>
);
};

How to Change pre filled input values

I new to React. I have two input fields which are filled when the pages loads via axios but when I want to change the Values I am unable to change them even I can not type anything on them.
function MainView() {
const [InputFields, setInputFields] = useState({
name: "",
fullURL: "",
});
const changeHandler = (e) => {
setInputFields({
...InputFields,
[e.target.name]: e.target.value,
});
};
const [links, setLinks] = useState([]);
const getLinks = () => {
axios.get('../sanctum/csrf-cookie').then(response => {
axios.get("/userlinks/getdata").then((res) => {
console.log(res);
setLinks(res.data);
}).catch((err) => {
console.log(err);
});
});
};
useEffect(() => {
getLinks();
}, []);
return (<div>
{links.map((link) => {
return (<div className="card" key={link.id}>
<form>
<input className="form-control" value={link.name} type="text" name="name" placeholder="name" onChange={changeHandler} />
<input className="form-control" value={link.fullURL} type="text" name="fullURL" placeholder="fullURL" onChange={changeHandler} />
</form>
</div>);
})}
</div>
);
}
export default MainView;
This happens because in your onChange method, you are changing InputFields variable, where as your getLinks method changes links variable, which is being rendered on the screen.
If you want to set an initial value, and then allow the user to change it, change your input to :
<input className="form-control" defaultValue={link.name}
value={InputFields.name} type="text" name="name" placeholder="name" onChange={changeHandler} />
Likewise change for your other input, if you do not want the user to change the value later on, it's often better to add disable in the input to avoid confusing people. 🙂
I know that this has been done so that you can create a minimal reproducible example for us, but I would have directly called setInputFields in the axios.get section to avoid this problem in the first place, however, if not possible, use the defaultValue and value as I've shown above.

React Ant Design form.resetFields() doesn't call onChange event of <Form.Items>

I'm having an Ant Design <Form> component with <Form.Items> which have onChange events. If the onChange event function is true I'm displaying extra content.
So in the example sandbox I created, when changing all the the <Radio> to Yes it fires the onChange event which is validated and then showing a div with the text "You checked all answered with yes".
As I'm using <Form> it is a form controlled environment so I'm using form to set and reset values. But when calling form.resetFields() the onChange handlers are not called. So the message won't go away as the state not refreshes. So I have to find a way to call a function from the parent component which refreshes the form values in the child component.
Using useImperativeHandle() for every field to update on more complex forms to call functions from the parent seems way too complex for such a simple task. And adding custom events to communicate with parent components seem to be a not very react way when reading this stack overflow thread
Is there something from the Ant Design form I'm missing? Because this must be a common task. What's a good way to approach this problem?
Link to code sandbox with an example:
https://codesandbox.io/s/vigilant-curran-dqvlc?file=/src/AntDFormChild.js
Example
const formLayout = {
labelCol: { span: 8 },
wrapperCol: { span: 7 }
};
const questionDefaultValues = {
rjr01_q01: 2,
rjr02_q01: 2
};
const AntDForm = () => {
const [form] = Form.useForm();
const handleResetForm = () => {
form.resetFields();
// now force onChange of child component to update
};
const handleFillForm = () => {
form.setFieldsValue({ rjr01_q01: 1, rjr02_q01: 1 });
// now force onChange of child component to update
};
return (
<>
<Button onClick={handleResetForm}>Reset Form</Button>
<Button onClick={handleFillForm}>Fill Form</Button>
<Form
{...formLayout}
form={form}
initialValues={{ ...questionDefaultValues }}
>
<AntDFormChild form={form} />
</Form>
</>
);
};
const questionQualifiedValues = {
rjr01_q01: 1,
rjr02_q01: 1
};
const AntDFormChild = ({ form }) => {
const [isQualified, setIsQualified] = useState(false);
const [questionFormValues, setQuestionFormValues] = useState({});
useEffect(() => {
if (shallowEqual(questionFormValues, questionQualifiedValues)) {
setIsQualified(true);
} else {
setIsQualified(false);
}
}, [questionFormValues]);
function shallowEqual(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (object1[key] !== object2[key]) {
return false;
}
}
return true;
}
return (
<>
{isQualified && (
<div style={{ color: "red" }}>You checked all answered with yes</div>
)}
<Form.Item name="rjr01_q01" label="Question 1">
<Radio.Group
onChange={(i) => {
setQuestionFormValues((questionFormValues) => ({
...questionFormValues,
rjr01_q01: i.target.value
}));
}}
>
<Radio value={1}>Yes</Radio>
<Radio value={0}>No</Radio>
<Radio value={2}>Unknown</Radio>
</Radio.Group>
</Form.Item>
<Form.Item name="rjr02_q01" label="Question 2">
<Radio.Group
onChange={(i) => {
setQuestionFormValues((questionFormValues) => ({
...questionFormValues,
rjr02_q01: i.target.value
}));
}}
>
<Radio value={1}>Yes</Radio>
<Radio value={0}>No</Radio>
<Radio value={2}>Unknown</Radio>
</Radio.Group>
</Form.Item>
</>
);
};
Since AntD Form is uncontrolled, there is no way to trigger onChange event by calling resetFields, setFieldsValues.
I think your goal is to show the message depending on form values, and the best way to do is to use Form.Item, which can access form state.
https://codesandbox.io/s/antd-form-item-based-on-other-item-ens59?file=/src/AntDFormChild.js

Clear sub-form values on conditional hide of nested fields

Using React+Formik, I want to create a reusable component that we can use to conditionally show/hide nested subforms (of any complexity).
Every time it becomes hidden, we wish to clear the values so that those values don't get submitted.
Below, a simple hide/show component called OptionalFeature is shown.
const OptionalFeature = ({
toggle,
children
}) => {
if (toggle) {
return <div>{children}</div>
} else {
return null;
}
}
It can be tested by pasting into https://codesandbox.io/s/zkrk5yldz
But as you can see in the demo, making the children invisible does not clear their values. Ideally each child can define it's own clearValue behavior (the example is very simple, we want to have more complex nested forms).
What's the clean solution to clear the fullname field by extending OptionalFeature class in a generic, reusable way?
I already tried creating a cleanup function and calling it from OptionalFeature inside the if-block, but it does not seem very idiomatic.
// Helper styles for demo
import "./helper.css";
import { DisplayFormikState } from "./helper";
import React from "react";
import { render } from "react-dom";
import { Formik } from "formik";
// Generic reusable component to show/hide sub-forms
const OptionalFeature = ({
toggle,
children
}) => {
if (toggle) {
return <div>{children}</div>
} else {
return null;
}
}
const App = () => (
<div className="app">
<Formik
initialValues={{ email: "", anonymous: false, fullname:"" }}
onSubmit={async values => {
await new Promise(resolve => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleSubmit
} = props;
return (
<form onSubmit={handleSubmit}>
<input
id="email"
placeholder="Enter your email"
type="text"
value={values.email}
onChange={handleChange}
/>
{/* This checkbox should show/hide next field */}
<div style={{ display: "white-space:nowrap" }}>
<label htmlFor="anonymous" style={{ display: "inline-block"}}>Anonymous</label>
<input
id="anonymous"
type="checkbox"
name="anonymous"
value={values.anonymous}
onChange={handleChange}
style={{ display: "inline-block", width: "20%"}}
/>
</div>
<OptionalFeature
toggle={!values.anonymous}
>
{/* Imagine this subform comes from a different file */}
<input
id="fullname"
placeholder="Enter your full name"
type="text"
value={values.fullname}
onChange={handleChange}
/>
</OptionalFeature>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
<DisplayFormikState {...props} />
</form>
);
}}
</Formik>
</div>
);
render(<App />, document.getElementById("root"));
Here is my existing approach, waiting for a better answer:
const OptionalFeature = ({
toggle,
onHide,
children
}) => {
if (toggle) {
return <div>{children}</div>
} else {
// useEffect needed because onHide function could trigger anything.
// Also to avoid calling multiple times.
useEffect(() => {
onHide();
}, [toggle, onHide])
return null;
}
}
Then later invoke a cleanup function onHide:
<Formik
initialValues={{ email: "", anonymous: false, fullname:"" }}
...>
{props => {
const {
values,
isSubmitting,
handleChange,
handleSubmit
} = props;
// to clean up all prop values inside the OptionalFeature
const clearFullName = () =>
{
values.fullname = ""
}
return (
//...
<OptionalFeature
toggle={!values.anonymous}
onHide={clearFullName} // using cleanup Function
>
<input
id="fullname"
placeholder="Enter your full name"
type="text"
value={values.fullname}
onChange={handleChange}
/>
</OptionalFeature>
);
}}
</Formik>
What I don't like here is that as the for becomes more complex with more OptionalFeatures or more elements nested inside the optional feature, it becomes quite hard to check whether all fields inside the nested optional form are being cleaned up or not. Also the properties of useEffect seem hard to test.
I would prefer some kind of nested subform such that I could write something like onHide={handleReset}, and this would be scoped only to fields inside the nested subform, without me having to define a custom handleReset function for that.

React - Update Value Of Input After Submit

I'm trying to make a feature where a user can edit a submitted value. So to be completely clear:
You would enter some text
Click submit and that value will be pushed into an array
You will be able to see your value on the dom
If you made an error, you can click on that input and change the value, also updating the state of that value in the already pushed array.
On a button click, you will update the state and have a newly edited value.
I'm stuck on the part of changing the state of the value of the pushed items in the array.
For example:
If I were to click on the field of 'Bob', edit it and click submit, the value of whatever I changed it to would also change the state of what was originally in my array to the new value.
This is what I have so far:
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
notes: ['hello', 'bob'],
val: ''
}
}
submit = () => {
const { notes, val } = this.state
notes.push(val)
this.setState({notes})
}
handleEdit = e => {
console.log(e)
}
render() {
return (
<div>
<input
type="text"
onChange={e => this.setState({val: e.target.value})}
/>
<button onClick={this.submit}>Submit</button>
{this.state.notes.map(item => {
return (
<form onSubmit={e => e.preventDefault()}>
<input
type="text"
defaultValue={item}
onChange={e => this.setState({val: e.target.value})}
/>
<button onClick={() => this.handleEdit(item)}>Submit
Change</button>
</form>
)
})}
</div>
)
}
}
Try this kind of thing :
handleEdit = (item) => {
const notes = this.state.notes.slice();
const index = notes.indexOf(item);
notes[index] = this.state.val;
this.setState({
notes
})
}

Categories

Resources