antd Form.Item accepts only one child - antd

I've created a little Fiddle to illustrate the issue: https://stackblitz.com/edit/react-avejvc-mmhqda?file=index.js
This form works:
<Form initialValues={{ surname: 'Mouse'}}>
<Form.Item name="surname">
<Input />
</Form.Item>
</Form>
This form doesn't:
<Form initialValues={{ surname: 'Mouse'}}>
<Form.Item name="surname">
<Input />
{null}
</Form.Item>
</Form>
The only difference is that the Form.Item in the second form has two children.
Is there an intention behind this?
In case anyone wonders why I am asking. So sth like this is breaking the form:
<Form.Item name={name}>
{type==="string" && <Input />}
{type==="integer" && <InputNumber />}
</Form.Item>

The official documentation here gives examples of using multiple children in one Form.Item.
<Form.Item label="Field">
<Form.Item name="field" noStyle><Input /></Form.Item> // that will bind input
<span>description</span>
</Form.Item>
You appear to have a problem with what you are putting in the Form.Item, ie. {null} may not be allowed.

I found a solution and have a better understanding now of what is going on.
From the docs (https://ant.design/components/form/#Form.Item):
After wrapped by Form.Item with name property, value(or other property defined by valuePropName) onChange(or other property defined by trigger) props will be added to form controls, the flow of form data will be handled by Form
There is a working example in the docs too, here is the codepen: https://codepen.io/pen?&editors=001
const { useState } = React;;
const { Form, Input, Select, Button } = antd;
const { Option } = Select;
const PriceInput = ({ value = {}, onChange }) => {
const [number, setNumber] = useState(0);
const [currency, setCurrency] = useState('rmb');
const triggerChange = (changedValue) => {
onChange?.({
number,
currency,
...value,
...changedValue,
});
};
const onNumberChange = (e) => {
const newNumber = parseInt(e.target.value || '0', 10);
if (Number.isNaN(number)) {
return;
}
if (!('number' in value)) {
setNumber(newNumber);
}
triggerChange({
number: newNumber,
});
};
const onCurrencyChange = (newCurrency) => {
if (!('currency' in value)) {
setCurrency(newCurrency);
}
triggerChange({
currency: newCurrency,
});
};
return (
<span>
<Input
type="text"
value={value.number || number}
onChange={onNumberChange}
style={{
width: 100,
}}
/>
<Select
value={value.currency || currency}
style={{
width: 80,
margin: '0 8px',
}}
onChange={onCurrencyChange}
>
<Option value="rmb">RMB</Option>
<Option value="dollar">Dollar</Option>
</Select>
</span>
);
};
const Demo = () => {
const onFinish = (values) => {
console.log('Received values from form: ', values);
};
const checkPrice = (_, value) => {
if (value.number > 0) {
return Promise.resolve();
}
return Promise.reject(new Error('Price must be greater than zero!'));
};
return (
<Form
name="customized_form_controls"
layout="inline"
onFinish={onFinish}
initialValues={{
price: {
number: 0,
currency: 'rmb',
},
}}
>
<Form.Item
name="price"
label="Price"
rules={[
{
validator: checkPrice,
},
]}
>
<PriceInput />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<Demo />, mountNode);

Related

I want to get my modified post, net ADD modified post on list (React - using react-redux)

// store.js
import { createStore, combineReducers } from "redux";
const INITIAL_STATE = [];
const postingReducer = (state, action) => {
if (action.type === "POST_SUCCESS") {
const newPost = {
title: action.payload.title,
content: action.payload.content,
};
return state.concat(newPost);
} else if (action.type === "POST_DELETE") {
return state.filter((item) => {
return item.title !== action.payload;
});
} else if (action.type === "POST_EDIT_SUCCESS") {
const modifiedPost = {
title: action.payload.title,
content: action.payload.content,
};
// console.log(modifiedPost)
// console.log(state.map((item,index)=>item[0]))
// console.log(state[0].title)
// state[0].title = modifiedPost.title
// state[0].content = modifiedPost.title
return state.concat(modifiedPost);
}
return INITIAL_STATE;
};
const store = createStore(
combineReducers({
posting: postingReducer,
})
);
export default store;
// EditForm.js
import { Link , useParams} from "react-router-dom";
import { useState } from "react";
import { useDispatch , useSelector } from "react-redux";
const EditForm = () => {
const dispatch = useDispatch();
const state = useSelector(state=>state.posting)
const params = useParams()
const [titleInput, setTitleInput] = useState(state[params.title].title);
const [contentInput, setContentInput] = useState(state[params.title].content);
const publishBtn = () => {
window.alert('Modified.')
return dispatch({
type: "POST_EDIT_SUCCESS",
payload: { title: titleInput, content: contentInput },
});
}
return (
<>
<form>
<div>
<label htmlFor="Title">Title</label>
<br />
<input
id="Title"
value={titleInput}
onChange={event=>{setTitleInput(event.target.value)}}
type="text"
/>
<br />
</div>
<br />
<div>
<label htmlFor="Content">Content</label>
<br />
<textarea
id="Content"
value={contentInput}
onChange={event=>{setContentInput(event.target.value)}}
type="text"
/>
<br />
</div>
<div>
<Link to="/">
<button onClick={publishBtn}>Modify</button>
</Link>
</div>
</form>
</>
);
};
export default EditForm
I want to get my modified post, net ADD modified post on list.
when i click Modify button in 'EditForm.js' I want to get my modified post, net ADD modified post on list.
in this situation, modified post added on the postlist.
I don't know how to fix "POST_EDIT_SUCCESS" return in 'store.js'
please help me!

Using useComboBox from DownShift with react-hook-form

I'm trying to use useComboBox from DownShift with react-hook-form and the value of the input is always undefined. I started with this: https://codesandbox.io/s/react-hook-form-controller-079xx?file=/src/DonwShift.js
And replaced the DownShift.js component with this: https://codesandbox.io/s/usecombobox-usage-1fs67?file=/src/index.js:168-438
Everything works except when I submit the value is undefined.What am I missing to set the value?
<form className="card" onSubmit={handleSubmit(handleShare)}>
<div className="body">
<Controller
as={Autocomplete}
control={control}
name="recipient"
items={userList}
/>
<button
className="secondaryActionBtn inputBtn"
type="submit"
enabled={String(formState.dirty)}
>
<FontAwesomeIcon icon={faPlus} />
</button>
{errors.lastname && 'Feed Name is required.'}
</div>
<footer></footer>
</form>
import React, { memo, useState } from 'react';
import PropTypes from 'prop-types';
import { useCombobox } from 'downshift';
const menuStyles = {
maxHeight: '180px',
overflowY: 'auto',
width: '135px',
margin: 0,
borderTop: 0,
background: 'white',
position: 'absolute',
zIndex: 1000,
listStyle: 'none',
padding: 0,
left: '135px'
};
const comboboxStyles = { display: 'inline-block', marginLeft: '5px' };
function Item({ isHighlighted, getItemProps, item, index }) {
return (
<li
style={isHighlighted ? { backgroundColor: '#bde4ff' } : {}}
key={`${item}${index}`}
{...getItemProps({ item, index })}
>
{item}
</li>
);
}
Item = memo(Item);
const Autocomplete = ({ items }) => {
const [inputItems, setInputItems] = useState(items);
const {
isOpen,
getToggleButtonProps,
getLabelProps,
getMenuProps,
getInputProps,
getComboboxProps,
highlightedIndex,
getItemProps
} = useCombobox({
items: inputItems,
onInputValueChange: ({ inputValue }) => {
setInputItems(
items.filter(item =>
item.toLowerCase().includes(inputValue.toLowerCase())
)
);
}
});
return (
<div>
<label htmlFor="recipient" {...getLabelProps()}>
Choose an element:
</label>
<div style={comboboxStyles} {...getComboboxProps()}>
<input name="recipient" {...getInputProps()} id="recipient" />
<button {...getToggleButtonProps()} aria-label="toggle menu">
↓
</button>
</div>
<ul {...getMenuProps()} style={menuStyles}>
{isOpen &&
inputItems.map((item, index) => (
<Item
key={item}
isHighlighted={highlightedIndex === index}
getItemProps={getItemProps}
item={item}
index={index}
/>
))}
</ul>
</div>
);
};
Autocomplete.propTypes = {
list: PropTypes.array
};
export default Autocomplete;
For others who get stuck on this here's how I solved it. The Controller in react-hook-form injects an onChange into the component as a prop. So i set the onSelectedItemChange prop in useCombobox hook to pass its value into onChange. Like this:
const {
isOpen,
getToggleButtonProps,
getLabelProps,
getMenuProps,
getInputProps,
getComboboxProps,
highlightedIndex,
getItemProps
} = useCombobox({
items: inputItems,
onSelectedItemChange: ({ inputValue }) => onChange(inputValue),
onInputValueChange: ({ inputValue }) => {
setInputItems(
items.filter(item =>
item.toLowerCase().includes(inputValue.toLowerCase())
)
);
}
});

React ant design typescript form validation not working

I used react typescript project to ant design and i used this ant design form validation, but its not working correctly, anyone know how to fix that issue?
Thanks
git a this error
index.tsx?789d:32 Uncaught TypeError: Cannot read property
'getFieldDecorator' of undefined
import * as React from "react";
import { Input, Form, Icon, Button, } from "antd";
import 'antd/dist/antd.css';
import "./style.css";
export class Registerform extends React.Component<any> {
handleSubmit = (e:any) => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err:any, values:any) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
/* Start add bulk upload form*/
<div className="remindersform-section">
<Form onSubmit={this.handleSubmit}>
<Form.Item
label={
<span>
Nickname
<Icon type="question-circle-o" />
</span>
}
>
{getFieldDecorator('nickname', {
rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
})(<Input />)}
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit">
Register
</Button>
</Form.Item>
</Form>
</div>
);
}
}
Because default form props are not assigned with their types, antd provided a solution for it, Try like below
import * as React from "react";
import { FormComponentProps } from "antd/es/form";
import { Input, Form, Icon, Button, } from "antd";
import 'antd/dist/antd.css';
interface UserFormProps extends FormComponentProps {
form: any;
}
class Registerform extends React.Component<UserFormProps> {
handleSubmit = (e:any) => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err:any, values:any) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
/* Start add bulk upload form*/
<div className="remindersform-section">
<Form onSubmit={this.handleSubmit}>
<Form.Item
label={
<span>
Nickname
<Icon type="question-circle-o" />
</span>
}
>
{getFieldDecorator('nickname', {
rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
})(<Input />)}
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit">
Register
</Button>
</Form.Item>
</Form>
</div>
);
}
}
const WrappedForm = Form.create<UserFormProps>({})(Registerform);
export default WrappedForm;

how can i get Select option value in antd

I have two select drop downs. If I click on One select option, it should be select and after click, without clicking second dropdown, i am not suppose to click update button. It should be show error.
import React from 'react'
import ReactDOM from 'react-dom'
import { Select, Button } from 'antd';
const Option =Select.Option
class SelectOption extends React.Component{
handleTeacherChange=(value)=>{
console.log(value)
}
handleCourseChange=(value)=>{
console.log(value)
}
render()
{
return(
<div align="center">
<div>
<h2>Shishu Bharathi</h2>
<label>Teacher List :</label>
<Select defaultValue="Select" style={{ width: 120 }} onChange={this.handleTeacherChange}>
<Option value="Vikram">Vikram</Option>
<Option value="Ramesh">Ramesh</Option>
</Select>
<label>Course List :</label>
<Select defaultValue="Select" style={{ width: 120 }} onChange={this.handleCourseChange}>
<Option value="cul1a">CUL1A</Option>
<Option value="cul1b">CUL1B</Option>
</Select>
</div>
<br></br>
<br></br>
<Button >Update</Button>
</div>
)
}
}
export default SelectOption
Use validateFields to check if a field is empty or not.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Select, Button, Form } from "antd";
const Option = Select.Option;
class SelectOption extends React.Component {
handleTeacherChange = value => {
console.log(value);
// this.props.form.validateField(["Dropdown2"]);
};
handleCourseChange = value => {
console.log(value);
// this.props.form.validateField(["Dropdown1"]);
};
updateClick = () => {
const { getFieldValue, validateFields } = this.props.form;
const dropdown1Value = getFieldValue("Dropdown1");
const dropdown2Value = getFieldValue("Dropdown2");
if (dropdown1Value === "Select" && dropdown2Value !== "Select") {
validateFields(["Dropdown1"]);
}
if (dropdown1Value !== "Select" && dropdown2Value === "Select") {
validateFields(["Dropdown2"]);
}
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div align="center">
<div>
<h2>Shishu Bharathi</h2>
<label>Teacher List :</label>
<Form.Item>
{getFieldDecorator("Dropdown1", {
initialValue: "Select",
rules: [
{ required: true, message: "Select the teacher" },
{
validator: (rule, value, callback) => {
console.log("value", value);
if (value === "Select") {
callback("Select the teacher");
}
callback();
}
}
]
})(
<Select
style={{ width: 120 }}
onChange={this.handleTeacherChange}
>
<Option value="Vikram">Vikram</Option>
<Option value="Ramesh">Ramesh</Option>
</Select>
)}
</Form.Item>
<label>Course List :</label>
<Form.Item>
{getFieldDecorator("Dropdown2", {
initialValue: "Select",
rules: [
{ required: true, message: "Select the course" },
{
validator: (rule, value, callback) => {
if (value === "Select") {
callback("Select the course");
}
callback();
}
}
]
})(
<Select style={{ width: 120 }} onChange={this.handleCourseChange}>
<Option value="cul1a">CUL1A</Option>
<Option value="cul1b">CUL1B</Option>
</Select>
)}
</Form.Item>
</div>
<br />
<br />
<Button onClick={this.updateClick}>Update</Button>
</div>
);
}
}
const A = Form.create()(SelectOption);
ReactDOM.render(<A />, document.getElementById("container"));
Here is a working demo :CodeSandbox
If you want show error when without clicking second dropdown, you need wrap it using formItem and validate it. Like follow code:
<FormItem
{...formItemLayout}
label={'month'}
>
{this.props.form.getFieldDecorator('loanMonth', {
initialValue: 3,
rules: [{
required: true, message: 'please select month!',
}],
})(
<Select>
{
this.formInitData &&
toJS(this.formInitData).loanMonthList.map((data) => {
return (
<Option value={data.key}>{data.value}</Option>
);
})
}
</Select>,
)}
</FormItem>
using the rules: [{required: true, message: 'please select month!'}] to get it.
Suggesting you see https://ant.design/components/form/

How to use flexigrid as a partial view in ASP.NET MVC application?

I am able to display Flexigrid in a normal view called from my main menu. I am using this sample http://mvc4beginner.com/Sample-Code/Insert-Update-Delete/Asp-.Net-MVC-Ajax-Insert-Update-Delete-Using-Flexigrid.html to make it work and it works fine for me.
However, my idea is to use a bit more complex interface - have a regular view with the search controls and on pressing search button show the grid with data for the items I searched.
I tried couple of things so far and can not make it to work. Here is the latest Index view I tried:
#model CardNumbers.Objects.Client
#{
ViewBag.Title = "Clients";
}
<h2>Clients</h2>
<br />
#using (Ajax.BeginForm("Search", "Client",
new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ClientsResults"
}))
{
<fieldset>
<legend>Search</legend>
<label for="clientNo">Client No: </label>
<input type="number" name="searchClientNo" class="numericOnly" /><br />
<label for="clientName">Client Name: </label>
<input type = "text" size =25 data-autocomplete="#Url.Action("QuickSearch", "Client")" name ="searchClientName" />
<div>
<input type="submit" value="Find / Refresh" />
#*<input type="button" value="Find / Refresh" id="ClientsSearch" data-url="#Url.Action("Client", "Client")" />
#*<input type="submit" value="Find / Refresh" />*#
#* #Ajax.ActionLink("Find / Refresh", "Client", new AjaxOptions {UpdateTargetId = "ClientResults",
InsertionMode = InsertionMode.Replace, HttpMethod = "POST"}) *#
#*}*#
</div>
</fieldset>
<div style="padding-left:150px; padding-top:50px; padding-bottom:50px;" id="ClientsResults">
#*#{Html.RenderPartial("_Client", Model); }*#
#*<table id="flexClients" style="display:none"/>*#
</div>
}
#*<br />*#
You can see all the commented attempts here also. So, the Search method in the Clients controller now has this code:
public ActionResult Search(int? searchClientNo = null, string searchClientName = null)
{
// Assume we want to select everything
var clients = Db.Clients; // Should set type of clients to IQueryable<Clients>
if ((searchClientNo ?? 0) != 0) //Number was supplied
clients = clients.Where(c => (c.Number == searchClientNo));
// If clientNo was supplied, clients is now filtered by that. If not, it still has the full list. The following will further filter it.
if (!String.IsNullOrWhiteSpace(searchClientName)) // Part of the name was supplied
clients = clients.Where(c => (c.Name.Contains(searchClientName)));
return PartialView("_ClientsSearch", clients);
//return PartialView("_Client", clients);
}
The commented view is the partial view which has a flexigrid and it's not working. The _ClientsSearch view is the "normal" index view created by using the template and this works.
Do you see what exactly I am missing? The flexigrid method is simply not firing at all when I attempt to use it as a partial view from that main view.
I haven't figured out the more complex scenario I had originally in mind, but I was able to make it work using the regular view. The helpful idea first came from this FAQ:
http://code.google.com/p/flexigrid/wiki/FAQ and also looking a bit closer to that sample I used.
So, now my Client view is this:
#model CardNumbers.Objects.Client
#{
ViewBag.Title = "Client";
}
<form id="frmClientsSearch">
<label for="clientNo">Client No: </label>
<input type="number" name="searchClientNo" class="numericOnly" /><br />
<label for="clientName">Client Name: </label>
<input type = "text" size =25 value ="Please enter the search value"
name ="searchClientName" />
<input type="button" id="btnClientsSearch" value ="Find / Refresh" />
</form>
<div style="padding-left: 150px; padding-top: 50px; padding-bottom: 50px;" id="ClientsResults">
<table id="flexClients" style="display: none">
</table>
</div>
<div style="display: none">
<form id="sform">
<input type="hidden" id="fntype" name="fntype">
<input type="hidden" id="Id" name="Id">
<label for="Number">Client No: </label>
<input type="number" id="Number" name="Number" class="numericOnly" />
<label for="Name">Client Name: </label>
<input type="text" size="25" id="Name" name="Name" /><br />
<label for="Contact11">Contact 1: </label>
<input type="text" size="25" id="Contact1" name="Contact1" /><br />
<div class="float-right">
<button type="Submit" id="btnSave">Submit</button>
<button type=reset id="btnCancel">Cancel</button>
</div>
</form>
</div>
And the main work is done in the .js file (note the AddFormData method):
/// <reference path = "jquery-1.5.1-vsdoc.js"/>
/// <reference path = "jquery-ui-1.8.11.js"/>
$(document).ready(function() {
$(":input[data-autocomplete]").each(function() {
$(this).autocomplete({ source: $(this).attr("data-autocomplete") });
});
});
$(function () {
$('input[name="delete"]').click(function () {
return confirm('Are you sure?');
});
});
$(".numericOnly").keypress(function (e) {
if (String.fromCharCode(e.keyCode).match(/[^0-9]/g)) return false;
});
$("#flexClients").flexigrid({
url: '/Client/Client/',
dataType: 'json',
colModel: [
{ display: 'Client Id', name: 'Id', width: 100, sortable: true, align: 'center', hide: true},
{ display: 'Client #', name: 'Number', width: 100, sortable: true, align: 'center' },
{ display: 'Name', name: 'Name', width: 350, sortable: true, align: 'center' },
{ display: 'Contact 1', name: 'Contact1', width: 350, sortable: true, align: 'center' },
],
buttons: [
{ name: 'Add', bclass: 'add', onpress: test },
{ name: 'Edit', bclass: 'edit', onpress: test },
{ name: 'Delete', bclass: 'delete', onpress: test },
{ separator: true }
],
searchitems: [
{ display: 'Client Name', name: 'Name' }
],
sortname: "Name",
sortorder: "asc",
usepager: true,
title: 'Clients',
useRp: true,
rp: 15,
showTableToggleBtn: true,
width: 1000,
onSubmit: addFormData,
height: 300
});
//This function adds parameters to the post of flexigrid. You can add a verification as well by return to false if you don't want flexigrid to submit
function addFormData() {
//passing a form object to serializeArray will get the valid data from all the objects, but, if the you pass a non-form object, you have to specify the input elements that the data will come from
var dt = $('#sform').serializeArray();
dt = dt.concat($('#frmClientsSearch').serializeArray());
$("#flexClients").flexOptions({ params: dt });
return true;
}
$('#sform').submit(function () {
$('#flexClients').flexOptions({ newp: 1 }).flexReload();
alert("Hello World");
return false;
});
function test(com, grid) {
if (com === 'Delete') {
var clientName = $('.trSelected td:eq(2)').text();
if (clientName) //Variable is defined and not empty
{
if (confirm("Are you sure you want to delete " + $.trim(clientName) + "?"))
return false;
$('#fntype').val('Delete');
$('#Id').val($('.trSelected td:eq(0)').text());
$('#Number').val('');
$('#Name').val('');
$('#Contact1').val('');
$('.trSelected', grid).each(function () {
var id = $(this).attr('id');
id = id.substring(id.lastIndexOf('row') + 3);
addFormData(); $('#flexClients').flexOptions({ url: '/Client/Client/' }).flexReload();
});
clearForm();
}
} else if (com === 'Add') {
$("#sform").dialog({
autoOpen: false,
show: "blind",
width: 1000,
height: 500
});
$("#sform").dialog("open");
$('#fntype').val('Add');
$('#Number').val('');
$('#Name').val('');
$('#Contact1').val('');
} else if (com === 'Edit') {
$('.trSelected', grid).each(function () {
$("#sform").dialog({
autoOpen: false,
show: "blind",
width: 1000,
height: 500
});
$("#sform").dialog("open");
$('#fntype').val('Edit');
$('#Id').val($('.trSelected td:eq(0)').text());
$('#Number').val($('.trSelected td:eq(1)').text());
$('#Name').val($('.trSelected td:eq(2)').text());
$('#Contact1').val($('.trSelected td:eq(3)').text());
});
}
}
function clearForm() {
$("#sform input").val("");
};
$(function () {
$('#btnSave').click(function () {
addFormData();
$('#flexClients').flexOptions({ url: '/Client/Client/' }).flexReload();
clearForm();
$('#sform').dialog('close');
return false;
});
});
$(function () {
$('#btnCancel').click(function () {
// clearForm();
$('#sform').dialog('close');
return false;
});
});
$(function () {
$('#btnClientsSearch').click(function () {
addFormData();
$('#flexClients').flexOptions({ url: '/Client/Client/' }).flexReload();
//$.ajax({
// url: $(this).data('url'),
// type: 'GET',
// cache: false,
// success: function (result) {
// $('#ClientsResults').html(result);
// }
//});
return;//false;
});
});
And my Client method in the controller is the same as it used to be with minor change.
Now, my next challenge is to generalize the above and also somehow instead of calling the form sForm I showed use a more complex form with validations as I if it is from the Create/Edit view.

Resources