How to store multiple react form data in state - javascript

I have a page where users can submit forms. They can choose to add new forms to the page(form 2, 3, 4, 5 of the same form type).
I handle that through:
handleAddPastMainData() {
const pastMainData = this.state.mainData[0].pastMainData;
this.setState({
mainData: {
...this.state.mainData,
pastMainData: pastMainData.push([])
}
})
}
Then I map through pastMainData to display the added form.
What I am having a challenge with is with the handler for saving the data since the form is nested.
My current state is like this:
this.state = {
mainData: [
{pastMainData: []},
]
};
And my handler is:
handleMainInputChange = (event) => {
const name = event.target.name;
const value = target.type === 'checkbox' ? target.checked : target.value;
this.setState(state => {
state.mainData[0].pastMainData[name] = value;
return state
})
};
A sample form input field in my app is:
<Field
label="Main name"
id="pastMainName"
name="pastMainName"
placeholder="Super Awesome FName"
value={state.MainData[0].pastMainData.pastMainName}
onChange={handleMainInputChange}
/>
I assign a key to each form when I map through pastMainData.
So a user can choose to add more than 5 of the above field input and submit.
When the data is submitted, I want the state to look like this:
this.state = {
mainData: [
{pastMainData: [[field1], [field2], [field3]},
]
};
When the user returns, this gives me the option to check number of fields/forms the user has already completed and render them with the saved data.
However, this obviously is not working.
Any way to improve my code and make it more efficient will be very appreciated.
Thanks

Currently when I run the code, on clicking Add Form, it renders the
form but continues to the next page. The call to continue to the next
page is only on the handleSubmit method.
To stop it from continuing to the next page you probably need to e.preventDefault() on that button.
"since the forms are multiple, how do I get the key and use that
particular key in the handleMainInputChange."
Add the key to the Field, or Form. For example:
<Field
key={key}
label="Main name"
id="pastMainName"
name="pastMainName"
placeholder="Super Awesome FName"
value={state.MainData[0].pastMainData.pastMainName}
onChange={handleMainInputChange}
/>
And you should be able to access it like you would any data-attr, if not, you can pass it as a a param to the onChange function.
also, maybe it would be easier to work with a different data structure for your state obj:
this.state = {
forms: {
form1: {},
form2: {},
form3: {},
}
};
may make setting and accessing easier in the long run.

Related

Validate antd form input in onChange with the value return by back-end API call

In my project I have antd form which used to add school details. In that form, I have ID field which need to be validated with back-end in onChange when input length = 5, whether that given ID is exists or not.
antd version 4.18.2
Field validations are handled via rules attribute in Form.item.
I use validator function to handle this custom validation.
State values
const [idStatus, setIdStatus] = useState(false); // this value is get by API call.
const [validateStatus, setValidateStatus] = useState("");
Form ID field
<Form form={form} name="form" onFinish={onFinish}>
<Row className="applicationFormContent">
<Col span={8}>
<Form.Item
name="schoolId"
label="School Id"
validateStatus={validateStatus}
rules={[
{
required: true,
message: "Please Provide School ID!"
},
{
len: 5,
message: "School ID must have 5 digits"
},
{
validator: validateId // validator is a function used for custom validation.
}
]}
>
<Input
type={"number"}
maxLength={5}
placeholder="Id - 00000 format"
onChange={(e) => checkSchoolId(e.target.value)}
/>
</Form.Item>
</Col>
</Row>
</Form>
Function triggered in onChange
const checkSchoolId = async (value) => {
try {
if (value.length === 5) {
let response = await checkId(value);
console.log("-----", response.response?.data?.idExists);
setIdStatus(response.response?.data?.idExists);
} else {
setValidateStatus("error");
}
} catch (err) {
console.log(err);
}
};
Validator function used in rules
const validateId = (rule, value) => {
if (idStatus) {
// value return from API call.
setValidateStatus("error");
return Promise.reject("Census Id Already Exist");
} else {
setValidateStatus("success");
return Promise.resolve();
}
};
Problem:
when the input length = 5, I get the value from backend and it updates the state. But validator function is not triggered with that update. Which means Form input is not being re-rendered when update the state variable.
i logged the values and updated values are shown as it is.
But when I type one more number (input-lenght=6), show both validations and then delete one character (now input-length=5), it shows the validation get from back-end value.
how to solve this?
example code sandbox
it's hard to say and please don't leave unlike if it's not true. but when you pass validator method in your rules like below:
validator: validateId
acutely you reference your validator into your method and the data and state that use in this method get initial state from first and never update. for solving this instead of passing reference to validator pass it as arrow-function and also pass your idStatus stat as arguments of method.
something like bellow
validator: (rule, value) => validateId(rule, value, idStatus)
with this approach you force your application to run this method each time want check validator.
There is a method named form.validateFields(['array of input names need to validate']) provided by antd Form for field validations.
With that I was able to re-execute the validation of the schoolId input when response come for idStatus inside a useEffect as below.
useEffect(() => {
// manually validate the field since input is not validated by form once state update.
form.validateFields(['schoolId']);
}, [idStatus]);
With the above way, we can manually trigger validation for particular Form.Item in antd Form wherever we want.

How to pass/delete array params in HTTP Params with Angular

I have an Array of statuses objects. Every status has a name, and a boolean set at false by default.
It represent checkbox in a form with filters, when a checkbox is checked bool is set at true :
const filters.statuses = [
{
name: "pending",
value: false
},
{
name: "done",
value: false
},
];
I am using Angular HTTP Params to pass params at the URL.
filters.statuses.forEach((status) => {
if (status.value) {
this.urlParams = this.urlParams.append('statuses[]', status.name);
}
});
Url params looks like when a status is checked :
&statuses%5B%5D=pending
My problem is when I want to unchecked.
I know HTTP Params is Immutable, so, I'm trying to delete the param when checkbox is unchecked, so set to false :
...else {
this.urlParams = this.urlParams.delete('statuses');
}
But, it not works, URL doesn't change.
And if I re-check to true after that, the URL looks like :
&statuses%5B%5D=pending&statuses%5B%5D=pending
How can I delete params, if the status value is false, and keep others statuses in URL ?
Project on Angular 10.
Thanks for the help.
UPDATE : It works to delete, my param name was not good :
else {
this.urlParams = this.urlParams.delete('statuses[]', status.name);
}
But, my other problem, it's when I check 2 or more checkbox, the append function write on URL : &statuses%5B%5D=pending&statuses%5B%5D=pending&statuses%5B%5D=done
I have prepared an example to try to answer your question (If I understand this right way).
You can change the checkboxes state or the URL to play with it. Also, I added helper buttons, which will navigate you to different cases (by changing the URL).
Here is the example: https://stackblitz.com/edit/angular-router-basic-example-cffkwu?file=app/views/home/home.component.ts
There are some parts. We will talk about HomeComponent.
You have ngFor which displays statuses, I handled state using ngModel (you can choose whatever you want).
You have a subscription to the activatedRoute.queryParams observable, this is how you get params and set up checkboxes (the model of the checkboxes)
You have the ngModelChange handler, this is how you change the route according to the checkboxes state
Let's focus on 2 & 3 items.
The second one. Rendering the correct state according to the route query params. Here is the code:
ngOnInit() {
this.sub = this.activatedRoute.queryParams.subscribe((params) => {
const statusesFromParams = params?.statuses || [];
this.statuses = this.statuses.map((status) => {
if (statusesFromParams.includes(status.name)) {
return {
name: status.name,
active: true,
};
}
return {
name: status.name,
active: false,
};
});
});
}
Here I parse the statuses queryParam and I set up the statuses model. I decide which is active and which is not here.
The third one. You need to update the URL according to the checkboxes state. Here is the code:
// HTML
<ng-container *ngFor="let status of statuses">
{{ status.name}} <input type="checkbox" [(ngModel)]="status.active" (ngModelChange)="onInputChange()" /> <br />
</ng-container>
// TypeScript
onInputChange() {
this.router.navigate(['./'], {
relativeTo: this.activatedRoute,
queryParams: {
statuses: this.statuses
.filter((status) => status.active)
.map((status) => status.name),
},
});
}
Here you have the ngModelChange handler. When any checkbox is checked/unchecked this handler is invoked. In the handler, I use the navigate method of the Router to change the URL. I collect actual checkboxes state and build the query parameters for the navigation event.
So, now you have a binding of the checkboxes state to the URL and vice versa. Hope this helps.

Using v-bind and v-on instead of v-model vuejs

I was using v-model to handle inputs in a form, I had to change it to bind props values, at first input was like
<input type="text" class="form-control" placeholder="" v-model="username">
and now it looks like
<input type="text" class="form-control" placeholder="" v-bind:value="modelData.username" v-on:input="username = $event.target.value">
modelData is coming in props. and it has username.
when Using model, in data, i had defined
data: () => {
return {
username: ""
}
},
and It was working fine, but after changing it to v-bind and v-on,
My question is how I can now get the value of username in methods? as in past, I was getting it as this.username to get the value and also clear it as well but now how I can get username in
methods: {}
I have a button to save input
<button class="btn btn-secondary" type="button" #click="validateFormAndSave($event)">Save</button>
When validateFormAndSave get called I can have this.username right now I cannot get the value? But the Vue Doc says v-model and v-bind:value& v-on:input are the same?
UPDATE:
There can be and cannot be some value already there in username, as it being filled with props value, So v-on:input="username = $event.target.value" don't get the already written value but the only new one you entered? or edit it? Why is it so? is there any method for just to get what anyone typed in there or already been typed?
UPDATE:
This is getting very ambiguous. So for now.
1. I can set v-bind:value, But I cannot get it in methods without editing it.
2. I cannot set this.username = "" and it will not be removed from input as well.
3. #input only get what you newly typed not what already in there
v-model is just syntax sugar for =>
<input :value="modelValue" #input="modelValue = $event.target.value"/>
If you want something else, it's very easy to do. Just change the update side to onInput:
<input
class="form-control"
:value="username"
#input="username = $event.target.value"
>
Then
data: () => {
return {
username: ""
}
},
mounted()
{
this.username = this.modelData.username;
},
methods:{
consoleUsername() {
console.log(this.username);
}
}
The best possible solution can be when you are getting your data from props and also loading it a form for v-models.
Using watch feature of Vue component.
First I added props as
export default {
props: ["vendorModelData"],
and then I pass it through the watch to v-model
watch: {
vendorModelData() {
this.updatePropsValue(this.vendorModelData)
console.log("data updated")
}
},
in this way, it always loads differently when Props get changed. This way I got be using v-model as well as load data from props to it.
I found it useful for me.

how to get field value on change for FormItem in antd

I am having a hard time with antd's form.
I have this select field in this form and I want to get the value from it onChange but somehow not getting it to work properly.
say this is the item for which I want the values
<FormItem
{...formItemLayout}
label={fieldLabels.qcategoryid}
validateStatus={categoryError ? "error" : ""}
help={categoryError || ""}
>
{getFieldDecorator("qcategoryid", {
rules: [{ required: true, message: "Please select Category!" }],
onChange: this.handleCategoryChange
})(<Select>{categoryOptions}</Select>)}
</FormItem>
this is the categoryOptions
if (this.props.categories) {
categoryOptions = this.props.categories.map(item => (
<Select.Option
key={item.categoryid}
value={item.categoryid}
name={item.categoryname}
>
{item.categoryname}
</Select.Option>
));
}
I want both the name of the category and the id
so I created a handleCategoryChange which gets called onChange
and I am able to get the fields I want.
But, it seems that now, I have to click twice on the field to properly select it.
If I click it just once then it does show up in the console. but the field on the form still remains empty. when I click it again, then the field shows up in the form too.
So, what am I doing wrong here.
Ah,yes! Here's the handleCategoryChange function
handleCategoryChange = (value, e) => {
console.log("value is : ", value);
console.log("e : ", e);
this.props.form.setFieldsValue({ qcategoryid: value });
this.setState({
categorySelected: value,
categoryname: e.props.name
});
};
Just to make myself clear.
I need those values before I click submit.
not on submit.
maybe this will help Ant Design form API as of 22nd of May 2022
This was added in v4.20
const Demo = () => {
const [form] = Form.useForm();
const userName = Form.useWatch('username', form);
const { data: options } = useSWR(`/api/user/${userName}`, fetcher);
return (
<Form form={form}>
<Form.Item name="username">
<AutoComplete options={options} />
</Form.Item>
</Form>
);
};
I realize this is super late, but I think this might be what OP was looking for:
https://github.com/react-component/form/blob/3b9959b57ab30b41d8890ff30c79a7e7c383cad3/examples/server-validate.js#L74-L79
To set fields on a form dynamically, e.g. in a child via a callback, you could use
this.props.form.setFields({
user: {
value: values.user,
errors: [new Error('forbid ha')],
},
});
in a parent defined handleSelect method you called from the child on a selected value. you can alternatively use setFieldsValue if you dont want to pass an error field
Try this:
<FormItem
{...formItemLayout}
label={fieldLabels.qcategoryid}
validateStatus={categoryError ? "error" : ""}
help={categoryError || ""}
>
{getFieldDecorator("qcategoryid", {
rules: [{ required: true, message: "Please select Category!" }]
})(<Select onChange={this.handleCategoryChange}>{categoryOptions}</Select>)}
</FormItem>
And on the handleCategoryChange function
handleCategoryChange = (value, e) => {
this.setState({
categorySelected: value,
categoryname: e.props.name
});
};
Basically, changing the onChange from the getFieldDecorator helper to the Select, so it doesn't mess with antd's natural behavior, but gets the value and change on state
I've based this answer on the code to the registration form on their website. Specifically, the handleWebsiteChange function
https://ant.design/components/form/#components-form-demo-register
I have made a helper function to solve this problem, you have to pass the field
name you want to take out from the form and the form object.
const getFieldValue = (fieldName,form) => {
return Form.useWatch(fieldName,form);
}
console.log(getFieldValue("username"))
in above example I had to console updated username value.
demo
A quick response, and hopefully a quick solution. Rather than using onChange you may want to use onSelect/onDeselect handlers, per the documentation (https://ant.design/components/select/):
<Select onSelect={handleCategoryChange} .../>
I have found also that SELECT and other input components, due to their custom html nature operate differently, and so in my forms, I have often created them as dummy fields that are using to alter text/hidden inputs in order to achieve the desired behaviours in complex forms.
Either I am doing something wrong, or the ANT way is mildly annoying.
Hope this helps.

Vue.js 2 - retrieve form elements from server and render CRUD

I'm Vue.js newbie and my task is:
make an ajax call (GET) to server, using RESTful API (Laravel on background)
retrieve a (JSON) list of Form CRUD items in array (like checkbox, input text, textarea...) with their properties (value, checked, custom classes...)
render CRUD form with these form items maybe using Vue's loop
I'm wondering if it could be rendered using components somehow. But I don't know the correct way.
Frankly, I exactly don't know how to solve this problem with Vue.js - rendering items from array and each item has it's own markup and properties (checkbox has it's own, textbox, select, textarea...).
I'm building a web application based on CRUD operations and I'm trying to write universal components. The easiest way is to do a special component with hard-written sub-components for each subpage, but I don't like this way if not needed.
Thank you!
EDIT: I don't have much code yet, but this is where I am...
<script>
// ./components/CrutList.vue
export default {
mounted() {},
data() {
return {
items: []
}
},
props: ['resource'],
methods: {
getItems() {
var resource = this.$resource('api/'+this.resource+'{/id}');
resource.get({}).then(function(items){
if(items.body.status == 'success'){
this.items = items.body.items;
}
}).bind(this);
},
deleteItem(item) {
// perform CRUD operation DELETE
alert('delete action');
}
}
}
</script>
My idea is using CrudList component to CRUD listing...
<crud-list resource="orders">
In laravel I do something like this:
return response()->json([
'status' => 'success',
'items' => [
[
'itemComponent' => 'checkbox',
'props' => [
'checked' => true,
'label' => "Checkbox č.1",
'name' => 'checkbox1'
]
],
[
'itemComponent' => 'checkbox',
'props' => [
'checked' => true,
'label' => "Checkbox č.2",
'name' => 'checkbox2'
]
],
[
'itemComponent' => 'checkbox',
'props' => [
'checked' => true,
'name' => 'checkbox3'
]
],
],
]);
...it's very simplified, but it's just example of what I'm doing.
Now the problem is:
take the 'itemComponent' part from the returned array item (this is in a loop),
if it's a checkbox, take (for example) Checkbox.vue component, fill it with properties ('props' part of the array item)
I read about slots, but it's not what I'm looking for. Is there something I can use for dynamic components?
Check out this jsFiddle working example for dynamic forms:
https://jsfiddle.net/mani04/kr8w4n73/1/
You can do it easily by using a lot of v-ifs for each and every form element type you might get from server. It is a bit cumbersome but I can't find any other way.
In the above example, I have the form structure as follows:
var formItems = [{
input_type: "text",
input_label: "Login",
values: {
value: "your_name#example.com"
}
},
{...},
{...}];
Once you have that data, then it is a matter of iterating through formItems, checking input_type and activating the relevant form control.
Here is how my dynamic form template looks like, for the above input:
<div v-for="formItem in formValues">
<div v-if="formItem.input_type == 'text'">
<input type="text" v-model="formItem.values.value">
</div>
<div v-if="formItem.input_type == 'password'">
<input type="password" v-model="formItem.values.value">
</div>
<div v-if="formItem.input_type == 'checkbox'">
<input type="checkbox" v-model="formItem.values.checked">
{{formItem.values.label}}
</div>
</div>
My jsFiddle example uses form-horizontal from bootstrap, and I am also able to display the labels well. If I put that in the example above, it will get cluttered and will not let you see how it works.
Hope it helps! You can change the formItems data structure to meet your needs, and modify the template accordingly.

Categories

Resources