Formik - arrayfields -- validation handling - material-ui

I am working with formik/material ui -- and yup validation. I am struggling to get validation showing/working on field arrays
my schema and validation looks like this currently for each field.
"fields": [
{
"type": "date",
"label": "Start Date",
"name": "startDate",
"validation": yup.date().default(function () { return new Date() }).required("date is required").nullable().typeError(''),
"minDate": moment().add(1, 'weeks'),
"maxDate": moment().add(8, 'weeks'),
"disablePast": true,
"disableFuture": false,
//"disabled": true
},
{
"type": "date",
"label": "End Date",
"name": "endDate",
"validation": yup.date().default(function () { return new Date() }).required("date is required").nullable().typeError(''),
"minDate": moment().add(1, 'weeks'),
"maxDate": moment().add(8, 'weeks'),
"disablePast": true,
"disableFuture": false,
//"disabled": true
}
]
I've seen on formik - they have some validation like this - but how do I apply it my code base for dates?
https://formik.org/docs/api/fieldarray
const schema = Yup.object().shape({
friends: Yup.array()
.of(
Yup.object().shape({
name: Yup.string().min(4, 'too short').required('Required'), // these constraints take precedence
salary: Yup.string().min(3, 'cmon').required('Required'), // these constraints take precedence
})
)
.required('Must have friends') // these constraints are shown if and only if inner constraints are satisfied
.min(3, 'Minimum of 3 friends'),
});
my fieldarray looks like this -- and I believe errors should appear under the field group -- the fields outer border goes red -- but it doesn't seem to work for when I null the date - like is required date working?
<>
<FieldArray
name={item.name}
onChange={event => {
console.log("event field array change", event)
}}
>
{({ insert, remove, push }) => (
<div className="field field-array">
<div className="row" key={0}>
{item.fields.map((ch, inx) => (
<span key={"x"+inx}>
<div className="col-x">
<Field
name={`${item.name}.${ch.name}`}
>
{({
field, // { name, value, onChange, onBlur }
form,
meta,
}) => (
<>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
label={ch.label}
disablePast={ch.disablePast}
disableFuture={ch.disableFuture}
minDate={moment(ch.minDate)}
maxDate={moment(ch.maxDate)}
value={field.value? moment(field.value).format('YYYY-MM-DD'): moment().format('YYYY-MM-DD')}
{...field}
onChange={(value) => {
form.setFieldValue(field.name, value);
this.props.onHandle(field.name, value);
}}
renderInput={(params) => {
return (<TextField {...params} name={field.name} />)
}}
/>
</LocalizationProvider>
{meta.touched && meta.error && (
<div className="error">{meta.error}</div>
)}
</>
)}
</Field>
</div>
{inx === 0 &&
(<span></span>)
}
</span>
))}
</div>
</div>
)}
</FieldArray>
</>

I worked this out
"validation": yup.array().of( yup.object().shape({ firstName: yup.string().min(4, 'too short').required('Required'), lastName: yup.string().min(3, 'cmon').required('Required'), }) ).min(1, 'Minimum of 1 friends')
-- but in the display of errors had to check if it was an array or a string to avoid a render error
under the add more button to display array errors of the main list.
<FormHelperText
error={(form.errors[parent.name] && form.errors[parent.name].length > 0 ? true : false)}
>
{typeof form.errors[parent.name] === "string" &&
<>{form.errors[parent.name]}</>
}
</FormHelperText>
and under the fields - meta errors
{(getHelperVisibility(values, ch)) &&
<FormHelperText
error={meta.touched && (meta.error && meta.error.length > 0 ? true : false)}
>
{meta.error}
</FormHelperText>
}

Related

Fomik FormGroup textfields

I am working on an application and I have made a form framework -- but there is a request to have dual fields.
Min and Max age
rather then having two fields -- "min_age" and "max_age" -- I think they would want an array field "age"
so instead of min_age: 18 and max_age: 33 --- I think they would want an array - age: [18, 33]
I have seen and implemented Radio and Checkbox groups.
-- but when I've tried to swap out the controlled field to a TextField -- the field is malfunctioning and not changing value.
this is what I have got -- as a textfield array group
<>
<FormGroup
row
name={item.name}
disabled={item.disabled}
{...field}
>
{
item.options.map((itm, j) => {
return (
<FormControlLabel key={j}
disabled={item.disabled}
control={
<div className="field field-text">
<TextField
fullWidth={false}
label={itm.label}
value={field.value[j]}
inputProps={{
maxLength: item.charLimit? item.charLimit:null,
autoComplete: item.autoComplete? item.autoComplete:"off"
}}
rows={(item.type === "comment") ? 6 : null}
multiline={(item.type === "comment") ? true : false}
/>
</div>
}
//label={itm.label}
onChange={(e, value) => {
//form.setFieldValue(item.name, value)
//this.props.onHandle(item.name, itm.value);
}}
/>
)
})
}
</FormGroup>
</>
and this is the radio group field that works just fine -- I've not seen any other example where textfields are controlled by the formgroup
<>
<RadioGroup
row
name={item.name}
{...field}
>
{
item.options.map((itm, j) => {
return (
<FormControlLabel key={j}
value={itm.value}
disabled={itm.disabled}
control={<Radio />}
label={itm.label}
onChange={(e, value) => {
//form.setFieldValue(item.name, value)
this.props.onHandle(item.name, itm.value);
}}
/>
)
})
}
</RadioGroup>
</>
I've tried to wrap tags around it - from this example -- but then the field_names are uncontrolled.
https://codesandbox.io/s/formik-multi-step-set-value-context-wrapper-sezzs?file=/src/App.js:2157-2449
:rf: : "111"
:rh: : "222"
age : 18
age_array : [18, 30]
button_field : "3"

React-hook-form + dynamic form: Render element upon dropdown selection

I am working in form using react-hook-form. This form use useFieldArray, it has to be dynamic.
Right now is very simple, it contains a react-select component with a few options and a textfield that get rendered depending on the option that the user select on the select component.
The problem I have is that the textfield component renders when the state updates, which is correct until I add a new group of element to the form. Since the textfield is listening to the same state it doesn't matter which select I use to render the textfield element, it gets rendered in all groups.
I am looking a way to specify which textfield should be rendered when the user change the select.
I the sandbox you can see what I have done. To reproduce the problem click on the "Add"-button and you will see two areas, each one with a select component.
When you choose "Other" in the select component a textfield appears, but not only in the area where the select was changed but in all areas.
How can I avoid that behavior?
https://codesandbox.io/s/vibrant-fast-381q0?file=/src/App.tsx
Extract:
const [isDisabled, setIsDisabled] = useState<boolean>(true);
const { control, handleSubmit, getValues } = useForm<IFormFields>({
defaultValues: {
managerialPositions: [
{
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
}
]
}
});
useFieldArray implementation:
const {
fields: managerialPositionsFields,
append: managerialPositionsAppend,
remove: managerialPositionsRemove
} = useFieldArray({
name: "managerialPositions",
control
});
Here i update the state when the user select "Other title" in the select component:
const watchChange = (value?: number, i?: number) => {
let values: any = getValues();
if (values.managerialPositions[i].title === 3) {
setIsDisabled(false);
}
};
And here is where I render the button to create a new group of elements and the select component and the textfield that should be rendered if "isDisabled" is false.
{managerialPositionsFields.map((field, index) => {
return (
<Stack className="sectionContainer" key={field.id}>
<Stack horizontal horizontalAlign="space-between">
<StackItem>
<CommandBarButton
iconProps={{ iconName: "AddTo" }}
text="Add"
type="button"
onClick={() => {
managerialPositionsAppend({
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
});
}}
/>
</StackItem>
</Stack>
<Stack horizontal tokens={{ childrenGap: 20 }}>
<StackItem>
<Label className="select-label requiredIkon">Title</Label>
<Controller
control={control}
name={`managerialPositions.${index}.title`}
render={({ field: { onChange, value, ref } }) => (
<>
<Select
className="react-select-container authoritySelect"
classNamePrefix="react-select"
placeholder="Select title"
options={titelList}
id={`managerialPositions.${index}.title`}
value={
titelList.find((g) => g.value === value)
? titelList.find((g) => g.value === value)
: null
}
onChange={(val) => {
onChange(val.value);
watchChange(val.value, index);
}}
/>
{
// this input is for select validation
<input
tabIndex={-1}
autoComplete="off"
style={{ opacity: 0, height: 0 }}
value={
titelList.find((g) => g.value === value)
? titelList
.find((g) => g.value === value)
.toString()
: ""
}
required={true}
//Without this console will get an error:
onChange={() => {}}
/>
}
</>
)}
/>
</StackItem>
{!isDisabled && (
<StackItem className="">
<Controller
name={`managerialPositions.${index}.otherTitle`}
control={control}
render={({
field: { onChange, name: fieldName, value }
}) => (
<TextField
label="Other title"
name={fieldName}
onChange={(e) => {
onChange(e);
}}
value={value}
/>
)}
/>
</StackItem>
)}
</Stack>
</Stack>
);
})}

Vuetify datatable reload when performing CRUD operations

I have a simple vuetify datatable that performs CRUD operations. I am using axios and a mongo database. When I add a new item, the information is correctly displayed in the client side and posted in the mongo database. However, I cannot access to updated information of the mongo database, particularly the id that mongo created, unless I reload the webpage. I am newcomer in Vue, please be patient. A simplified version of the problem:
axios
.post('http://localhost:5000/dessert', {
name: this.editedItem.name
})
console.log(this.editedItem.name) // I CAN ACCES WITHOUT PROBLEM
console.log(this.editedItem._id) // I NEED TO RELOAD THE WEBPAGE IN ORDER TO ACCES THIS ELEMENT. THE ID THAT MONGO CREATED.
Vue file:
<template>
<v-data-table
:headers="headers"
:items="desserts"
sort-by="calories"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>My CRUD</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on }">
<v-btn color="primary" dark class="mb-2" v-on="on">New Item</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.name" label="Dessert name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.calories" label="Calories"></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close">Cancel</v-btn>
<v-btn color="blue darken-1" text #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.action="{ item }">
<v-icon
small
class="mr-2"
#click="editItem(item)"
>
edit
</v-icon>
<v-icon
small
#click="deleteItem(item)"
>
delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</template>
<script>
import axios from 'axios'
export default {
data: () => ({
dialog: false,
headers: [
{
text: 'Dessert (100g serving)',
value: 'name',
},
{ text: 'Calories', value: 'calories' },
{ text: 'Actions', value: 'action', sortable: false },
],
desserts: [],
editedIndex: -1,
editedItem: {
name: '',
calories: 0,
},
defaultItem: {
name: '',
calories: 0,
},
}),
mounted() {
this.fetchItems()
},
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
watch: {
dialog (val) {
val || this.close()
},
},
created () {
this.initialize()
},
methods: {
fetchItems(){
axios
.get('http://localhost:5000/dessert')
.then(response => (this.desserts = response.data.data))
},
editItem (item) {
this.editedIndex = this.desserts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.editedID = this.editedItem._id
this.name = this.editedItem.name
this.calories = this.editedItem.calories
this.dialog = true
},
deleteItem (item) {
const index = this.desserts.indexOf(item)
this.deletedItem = Object.assign({}, item)
console.log(this.deletedItem)
this.deletedID = this.deletedItem._id
console.log(this.deletedID)
if (confirm("Do you really want to delete?")) {
axios.delete(`http://localhost:5000/dessert/${this.deletedID}`);
this.desserts.splice(index, 1);
}
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () { // Edit Item
if (this.editedIndex > -1) {
Object.assign(this.desserts[this.editedIndex], this.editedItem)
axios.delete(`http://localhost:5000/dessert/${this.editedItem._id}`)
axios
.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
})
// New Item
} else {
this.desserts.push(this.editedItem)
axios.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
})
}
this.close()
},
},
}
</script>
Python file:
from flask import Flask
from flask import jsonify
from flask import request
from flask_pymongo import PyMongo
from flask_cors import CORS
from bson.objectid import ObjectId
app = Flask(__name__)
#CORS(app)
# instantiate
app.config.from_object(__name__)
# enable CORS
CORS(app, resources={r'/*': {'origins': '*'}})
app.config['MONGO_DBNAME'] = 'restdb'
app.config['MONGO_URI'] = 'mongodb://localhost:27017/restdb'
mongo = PyMongo(app)
#app.route('/dessert', methods=['POST'])
def add_dessert():
dessert = mongo.db.desserts
name = request.json['name']
calories = request.json['calories']
dessert_id = dessert.insert({
'name': name,
'calories': calories
})
new_dessert = dessert.find_one({'_id': dessert_id })
output = {
'name' : new_dessert['name'],
'calories' : new_dessert['calories']
}
return jsonify({'result' : output})
#app.route('/dessert', methods=['GET'])
def get_all_desserts():
dessert = mongo.db.desserts
output = []
for s in dessert.find():
s['_id'] = str(s['_id'])
output.append({'_id' : s['_id'],
'name' : s['name'],
'calories' : s['calories']
})
return jsonify({'data' : output})
#app.route('/dessert/<dessert_id>', methods=['GET'])
def get_one_dessert(dessert_id):
dessert = mongo.db.desserts
s = dessert.find_one({"_id" : ObjectId(dessert_id)})
s['_id'] = str(s['_id'])
if s:
output = {'_id' : s['_id'], 'name' : s['name'], 'calories' : s['calories']}
else:
output = "No such name"
return jsonify({'result' : output})
#app.route('/dessert/<dessert_id>', methods=['DELETE'])
def delete_one_dessert(dessert_id):
dessert = mongo.db.desserts
s = dessert.find_one({"_id" : ObjectId(dessert_id)})
s['_id'] = str(s['_id'])
dessert.remove({"_id" : ObjectId(dessert_id)})
if s:
output = {'_id' : s['_id'], 'name' : s['name'], 'calories' : s['calories']}
else:
output = "No such name"
return jsonify({'result' : output})
if __name__ == '__main__':
app.run(debug=True)
If I understood it correctly, you want to be able to see in the front-end the newly added item, including the generated ID, after posting it to backend, right?
So, you can just simply call the fetchItems() once you finish posting the new item. It will automaticly update the array of the shown items, including the newly added ID.
The ID property is created when the item is added to the database, so it's not possible to have it unless the back-end gives it back to the front-end.
axios.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
}).then(response => {
this.fetchItems()
})
That means, once finishing the POST, fetchItems() again.

Create autocomplete box in Syncfusion

How to create autocomplete box when edit mode is inlineFormTemplate
for eg:
<script id="template" type="text/template">
<input type="text" name="test" value="{{:test}}"/>
//here i need autocomplete textbox like this
<ej-autocomplete id="search1" filter-type="Contains" highlight-search="true" show-rounded-corner="true" enable-auto-fill="true"
enable-distinct="true" show-popup-button="true" watermark-text="Country name" items-count="20" min-character="2"
width="150" popup-width="500px" popup-height="250px"
template="<div width='5%'>${CountryName} ${CountryId}</div>">
<e-autocomplete-fields key="CountryId" text="CountryName" />
<e-datamanager adaptor="UrlAdaptor" url="/country/SelectCountry"></e-datamanager>
</ej-autocomplete>
</script>
We would like to inform you that, In Asp.Net Core, control has been rendered initially. When using the render Template concepts, the control will not be created. To handle this, we have achieved your requirement by rendering the Autocomplete control from client side.Please find code of the sample in the Grid to use the Autocomplete in a Grid column when Editing.
Code:
<ej-grid id="Edittemplate" allow-paging="true">
<e-datamanager url="//mvc.syncfusion.com/Services/Northwnd.svc/Orders/?$top=45" offline="true"></e-datamanager>
<e-edit-settings allow-adding="true" allow-deleting="true" allow-editing="true" edit-mode="Normal" />
<e-toolbar-settings show-toolbar="true" toolbar-items='new List<string>() { "add", "edit", "delete", "update", "cancel", "search" }' />
<e-columns>
<e-column field="OrderID" is-primary-key="true" header-text="Order ID" text-align="Right" width="70"></e-column>
<e-column field="CustomerID" header-text="Customer ID" width="80">
<e-edit-template create="create" read="read" write="write"></e-edit-template>
</e-column>
<e-column field="EmployeeID" header-text="Employee ID" text-align="Left" width="75"></e-column>
<e-column field="Freight" header-text="Freight" text-align="Right" format="{0:C2}" width="75"></e-column>
<e-column field="OrderDate" header-text="Order Date" text-align="Right" width="80" format="{0:MM/dd/yyyy}"></e-column>
<e-column field="ShipCity" header-text="Ship City" width="110"></e-column>
</e-columns>
</ej-grid>
<script type="text/javascript">
function create() {
return $("<input>");
}
function write(args) {
obj = $('#Edittemplate').ejGrid('instance');
args.element.ejAutocomplete({
width: "100%", dataSource: obj.model.dataSource,
query: ej.Query().from("Suppliers").select("CustomerID"),
filterType: "contains",
multiColumnSettings: {
stringFormat: "{0} ({2}) ({1})",
enable: true,
showHeader: true,
columns: [{
field: "CustomerID",
headerText: "CustomerID",
},
{
field: "OrderID",
headerText: "OrderID"
},
{
field: "EmployeeID",
headerText: "EmployeeID"
},
{
field: "ShipCity",
headerText: "ShipCity"
}
]
}, value: args.rowdata !== undefined ? args.rowdata["CustomerID"] : ""
});
}
function read(args) {
args.ejAutocomplete('suggestionList').css('display', 'none');
return args.ejAutocomplete("getValue");
}
$("#Edittemplate").keyup(function (e) {
if (e.keyCode == 40 && $(e.target).hasClass("e-autocomplete")) {
var autocomp = $("#EdittemplateCustomerID").ejAutocomplete("instance")
if (autocomp.getValue() != "" && autocomp.getActiveText() != "No suggestions")
$(e.target).val(autocomp.getActiveText());
}
});

RadioGroup not working in redux-form

I am using the custom radio button component in redux-form. I am not able to make it work properly. Following the custom component code.
import React, { PropTypes } from 'react';
import {
Col,
ControlLabel,
FormGroup,
Label,
} from 'react-bootstrap';
import { RadioGroup, Radio } from 'react-radio-group';
import _ from 'lodash';
import './style.css';
const FormRadioGroup = props => (
<FormGroup controlId={props.name || ''}>
<Col
componentClass={ControlLabel}
sm={props.labelColValue || 2}
className={props.labelClass || ''}
>
{ props.label || 'Checkbox Label'}
</Col>
<Col sm={props.radioColValue || 6}>
<RadioGroup
name={props.name}
selectedValue={props.input.value}
onChange={value => { props.input.onChange(value); }}
className="react-radio-group-button"
>
{
_.map(props.options, option =>
(
<Label>
<Radio value={option.key} />
{option.value}
</Label>
))
}
</RadioGroup >
</Col>
</FormGroup>
);
FormRadioGroup.propTypes = {
name: PropTypes.string,
labelColValue: PropTypes.number,
labelClass: PropTypes.string,
label: PropTypes.string,
radioColValue: PropTypes.number,
options: PropTypes.array,
disabled: PropTypes.boolean,
radioClass: PropTypes.string,
sendBoolean: PropTypes.boolean,
input: PropTypes.object,
meta: PropTypes.object,
};
export default FormRadioGroup;
In redux-form I am using as follows.
<Field
name="json_boolean_property"
component={FormRadioGroup}
format={value => (value ? 'yes':'no')}
parse={value => (value === 'yes')}
type="radio"
labelColValue={2}
label="Radio Group Label"
radioBoxColValue={6}
sendBoolean
options={[
{ key: 'yes', value: 'Yes' },
{ key: 'no', value: 'No' },
]}
/>
I am not able to set the default value when it comes form the server also when i select the radio button it fire the change event but along with that it also give redux-form/UPDATE_SYNC_ERRORS.
I am not sure, what wrong I am doing but its failing very badly.