I have a main component and a child component where I use antd table.
The code does not throw any exception, and the selected ids are flowing correctly to the main component.
However the checkboxes are cleared after clicking on them, so I am not sure what am I missing here
Main component
import React, { Component } from 'react';
import { Input} from 'antd';
import Form from '../../components/uielements/form';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
import ListPageTemplatesWithSelection from './ListPageTemplatesWithSelection';
const FormItem = Form.Item;
class CreateModernSiteCollectionForm extends Component {
constructor(props) {
super(props);
this.state = {Alias:'',DisplayName:'', Description:'', PageTemplateIds : []};
this.handleChangeAlias = this.handleChangeAlias.bind(this);
this.handleChangeDisplayName = this.handleChangeDisplayName.bind(this);
this.handleChangeDescription = this.handleChangeDescription.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRowSelect = this.handleRowSelect.bind(this);
}
handleRowSelect(ids) {
this.setState({ PageTemplateIds: ids });
}
handleChangeAlias(event){
this.setState({Alias: event.target.value});
}
handleChangeDisplayName(event){
this.setState({DisplayName: event.target.value});
}
handleChangeDescription(event){
this.setState({Description: event.target.value});
}
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
let data = new FormData();
//Append files to form data
//data.append(
const options = {
method: 'post',
body: JSON.stringify(
{
"Alias": this.state.Alias,
"DisplayName": this.state.DisplayName,
"Description": this.state.Description
}),
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
adalApiFetch(fetch, "/SiteCollections", options)
.then(response =>{
if(response.status === 201){
Notification(
'success',
'Site collection created',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Site collection not created',
error
);
console.error(error);
});
}
});
}
render() {
// rowSelection object indicates the need for row selection
const handleRowSelect = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(selectedRowKeys);
}
};
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Alias" hasFeedback>
{getFieldDecorator('Alias', {
rules: [
{
required: true,
message: 'Please input your alias',
}
]
})(<Input name="alias" id="alias" onChange={this.handleChangeAlias} />)}
</FormItem>
<FormItem {...formItemLayout} label="Display Name" hasFeedback>
{getFieldDecorator('displayname', {
rules: [
{
required: true,
message: 'Please input your display name',
}
]
})(<Input name="displayname" id="displayname" onChange={this.handleChangeDisplayName} />)}
</FormItem>
<FormItem {...formItemLayout} label="Description" hasFeedback>
{getFieldDecorator('description', {
rules: [
{
required: true,
message: 'Please input your description',
}
],
})(<Input name="description" id="description" onChange={this.handleChangeDescription} />)}
</FormItem>
<ListPageTemplatesWithSelection onRowSelect={this.handleRowSelect} />
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
Create modern site
</Button>
</FormItem>
</Form>
);
}
}
const WrappedCreateModernSiteCollectionForm = Form.create()(CreateModernSiteCollectionForm);
export default WrappedCreateModernSiteCollectionForm;
Child component
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListPageTemplatesWithSelection extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/PageTemplates", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.Id,
Name: row.Name
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render(){
const columns = [
{
title: 'Id',
dataIndex: 'key',
key: 'key',
},
{
title: 'Name',
dataIndex: 'Name',
key: 'Name',
}
];
const rowSelection = {
selectedRowKeys: this.props.selectedRows,
onChange: (selectedRowKeys) => {
this.props.onRowSelect(selectedRowKeys);
}
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListPageTemplatesWithSelection;
This is happening because child component is expecting the selectedRows prop but the main component is not passing it to it.
const rowSelection = {
selectedRowKeys: this.props.selectedRows,
//-----------------------------^^--------
onChange: (selectedRowKeys) => {
this.props.onRowSelect(selectedRowKeys);
}
};
So when you select something, parent updates its state via onRowSelect prop. But it forgets to send the updated state back to the child via selectedRows prop. So the child doesn't know what row's have been selected and fallbacks to default unchecked behavior.
To fix it, just pass the PageTemplateIds state as selectedRows prop in your main component:
<ListPageTemplatesWithSelection
onRowSelect={this.handleRowSelect}
selectedRows={this.state.PageTemplateIds}
/>
Related
Im using Antd library and i can't seem to find where i have the bug.
This is my EditableTableCell component
import React, {Component} from 'react';
import { Form } from '#ant-design/compatible';
import '#ant-design/compatible/assets/index.css';
import { Input, InputNumber, Select, DatePicker } from "antd";
import moment from "moment";
import {EditableContext} from "./EditableTableRow";
const FormItem = Form.Item;
const Option = Select.Option;
class EditableTableCell extends Component {
getInput = (record, dataIndex, title, getFieldDecorator) => {
switch (this.props.inputType) {
case "number":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(
<InputNumber formatter={value => value} parser={value => value} />
)}
</FormItem>
);
case "date":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
initialValue: moment(record[dataIndex], this.dateFormat)
})(<DatePicker format={this.dateFormat} />)}
</FormItem>
);
case "select":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
initialValue: record[dataIndex]
})(
<Select style={{ width: 150 }}>
{[...Array(11).keys()]
.filter(x => x > 0)
.map(c => `Product ${c}`)
.map((p, index) => (
<Option value={p} key={index}>
{p}
</Option>
))}
</Select>
)}
</FormItem>
);
default:
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(<Input />)}
</FormItem>
);
}
}
render() {
const { editing, dataIndex, title, inputType, record, index,...restProps} = this.props;
return (
<EditableContext.Consumer>
{form => {
const { getFieldDecorator } = form;
return (
<td {...restProps}>
{editing ?
this.getInput(record, dataIndex, title, getFieldDecorator)
: restProps.children}
</td>
);
}}
</EditableContext.Consumer>
);
}
}
export default EditableTableCell;
This is my EditableTableCell component
import React, {Component} from 'react';
import { Form} from '#ant-design/compatible';
export const EditableContext = React.createContext();
class EditableTableRow extends Component {
render() {
return (
<EditableContext.Provider value={this.props.form}>
<tr {...this.props} />
</EditableContext.Provider>
);
}
}
export default EditableTableRow=Form.create()(EditableTableRow);
This is my ProductsPage component im having bug in
import React, {Component} from 'react';
import {Button, Layout, notification, Popconfirm, Space, Table,Typography} from "antd";
import {Link} from "react-router-dom";
import {Content} from "antd/es/layout/layout";
import EditableTableRow, {EditableContext} from "../components/EditableTableRow";
import EditableTableCell from "../components/EditableTableCell";
import API from "../server-apis/api";
import {employeesDataColumns} from "../tableColumnsData/employeesDataColumns";
import {CheckCircleFilled, InfoCircleFilled} from "#ant-design/icons";
class ProductsPage extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
error: null,
isLoaded: false,
editingKey: "",
errorMessage: "",
}
}
columns = [
...employeesDataColumns,
{
title: "Actions",
dataIndex: "actions",
width: "10%",
render: (text, record) => {
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{form => (<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>Save</a>)}
</EditableContext.Consumer>
<a onClick={this.cancel}>Cancel</a>
</span>
) : (
<Space size="middle">
<a onClick={() => this.edit(record.username)}>Edit</a>
<Popconfirm title="Are you sure you want to delete this product?"
onConfirm={() => this.remove(record.username)}>
<a style={{color:"red"}}>Delete</a>
</Popconfirm>
</Space>
);
},
}
];
isEditing = (record) => {
return record.username === this.state.editingKey;
};
edit(username) {
this.setState({editingKey:username});
}
cancel = () => {
this.setState({ editingKey: ""});
};
componentDidMount() {
this.setState({ loading: true });
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
API.get(`users/all`,{ headers: { Authorization: token}})
.then(res => {
// console.log(res.data._embedded.productList);
const employees = res.data._embedded.employeeInfoDtoList;
this.setState({loading: false,data:employees });
})
}
async remove(username) {
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
API.delete(`/users/${username}`,{ headers: { Authorization: token}})
.then(() => {
let updatedProducts = [...this.state.data].filter(i => i.username !== username);
this.setState({data: updatedProducts});
this.successfullyAdded("Employee is deleted. It wont have any access to the website anymore.")
}).catch(()=>this.errorHappend("Failed to delete"));
}
hasWhiteSpace(s) {
return /\s/g.test(s);
}
saveData(form,username) {
form.validateFields((error, row) => {
if (error) {
return;
}
const newData = [...this.state.data];
const index = newData.findIndex(item => username === item.username);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row
});
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
const response = API.put(`/users/${username}/update`, row,{ headers: { Authorization: token}})
.then((response) => {
this.setState({ data: newData, editingKey: ""});
this.successfullyAdded("Empolyee info is updated")
})
.catch(error => {
this.setState({ errorMessage: error.message });
this.errorHappend("Failed to save changes.")
console.error('There was an error!', error);
});
});
}
successfullyAdded = (message) => {
notification.info({
message: `Notification`,
description:message,
placement:"bottomRight",
icon: <CheckCircleFilled style={{ color: '#0AC035' }} />
});
};
errorHappend = (error) => {
notification.info({
message: `Notification`,
description:
`There was an error! ${error}`,
placement:"bottomRight",
icon: <InfoCircleFilled style={{ color: '#f53333' }} />
});
};
render() {
const components = {
body: {
row: EditableTableRow,
cell: EditableTableCell
}
};
const columns = this.columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => {
const checkInput = index => {
switch (index) {
case "price":
return "number";
default:
return "text";
}
};
return {
record,
// inputType: col.dataIndex === "age" ? "number" : "text",
inputType: checkInput(col.dataIndex),
dataIndex: col.dataIndex,
title: col.title,
editing: this.isEditing(record)
};
}
};
});
const { data, loading } = this.state;
return (
<Layout>
<div>
<Link to="/add-product">
<Button style={{float:"right", background: "#0AC035",marginBottom:"1em", marginTop:"1em" }}
type="primary">New emplyee</Button>
</Link>
</div>
<Content>
<Table components={components} bordered dataSource={data} columns={columns} loading={loading} rowKey={data.username} rowClassName="editable-row"/>
</Content>
</Layout>
);
}
}
export default ProductsPage;
This is the bug I'm having:
enter image description here
And i want to have this result like its shown in Antd docs:
enter image description here
Id really appreciate if you take a look and help me figure out where im wrong
Updated Solution:
I find the issue. In render where you map the columns, you just return the column if it's not an editable column. You can check the code below. I added a check if it's dataIndex === 'actions', then return the following code:
Please Follow the link:
https://react-ts-v3fbst.stackblitz.io
Changes:
1.In columns, i remove the render function from the action object:
{
title: 'Actions',
dataIndex: 'actions',
width: '10%',
},
2. In render function where you map the columns, add the following code before this condition if(!col.editable) {,:
if (col.dataIndex === 'actions') {
return {
...col,
render: (text, record) => {
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{(form) => (
<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>
Save
</a>
)}
</EditableContext.Consumer>
<a onClick={this.cancel}>Cancel</a>
</span>
) : (
<Space size='middle'>
<a onClick={() => this.edit(record.username)}>Edit</a>
<Popconfirm title='Are you sure you want to delete this product?' onConfirm={() => this.remove(record.username)}>
<a style={{ color: 'red' }}>Delete</a>
</Popconfirm>
</Space>
);
}
};
}
When you click on edit, you set the username as key for that particular row for editing, make sure you have username in each record. I tested this using the following data:
const data = [
{ id: 8, name: 'baun', model: '2022', color: 'black', price: 358, quantity: 3, username: 'brvim' },
{ id: 3, name: 'galileo', model: '20221', color: 'white', price: 427, quantity: 7, username: 'john' }
];
Most important, you should select that attribute as key that is unique in all records. As you are using username, i don't know what is your business logic or data looks like, but technically each record can have same username. So you must select something that would always be unique in your complete data.
// index.js
import React, { Component } from "react";
import MaterialTable, { MTableEditRow } from "material-table";
import axios from "axios";
import DataModel from "./DataModel";
import TitleInput from "./TitleInput";
class Report extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
workOrderOptions: [],
newEntry: {
userId: "",
id: "",
title: "",
body: ""
}
};
this.handleNewTitle = this.handleNewTitle.bind(this);
this.cancelAdd = this.cancelAdd.bind(this);
}
renderData() {
const URL = "https://jsonplaceholder.typicode.com/posts";
axios
.get(URL)
.then(response => {
this.setState({
data: response.data
});
})
.catch(error => {
console.log("ERROR:", error);
});
}
// I want to fire this method upon canceling the "add row"
cancelAdd() {
this.setState({
newEntry: {
userId: "",
id: "",
title: "",
body: ""
}
});
}
handleNewTitle(title) {
this.setState({
newEntry: {
// ...this.state.newEntry.title,
title: title
}
});
}
componentDidMount() {
this.renderData();
}
render() {
const columns = [
{
title: "ID",
field: "id",
editable: "never"
},
{
title: "User ID",
field: "userId",
editable: "never"
},
{
title: "Title",
field: "title",
editable: "never"
},
{
title: "Body",
field: "body",
editable: "never"
}
];
if (this.state.data) {
return (
<div>
<MaterialTable
components={{
EditRow: props => {
return (
<div>
<TitleInput
value={this.state.newEntry.title}
title={this.handleNewTitle}
/>
{/* <BodyInput
value={this.state.newEntry.body}
body={this.handleNewBody}
/>, <UserIDInput />, etc... */}
<MTableEditRow
{...props}
data={this.state.newEntry}
// Is there a handleCancelAction (or something ma something)?
</div>
);
}
}}
editable={{
// Just a sample add
onRowAdd: newData =>
new Promise((resolve, reject) => {
const result = {
id: 15465,
userId: 87946542,
title: this.state.newEntry.title,
body: "Old man Santiago"
};
console.log(result);
const data = this.state.data;
data.push(result);
this.setState({
...this.state
});
resolve();
})
}}
data={this.state.data}
columns={columns}
title={"Title"}
/>
</div>
);
} else if (!this.state.data) {
return <div>Loading...</div>;
}
}
}
export default Report;
// TitleInput.js
import React, { Component } from "react";
class TitleInput extends Component {
constructor(props) {
super(props);
this.handleTitleChanges = this.handleTitleChanges.bind(this);
}
handleTitleChanges(event) {
const title = event.target.value;
this.props.title(title);
}
render() {
return (
<div>
<select onChange={this.handleTitleChanges}>
<option selected hidden />
<option value="Old Man and the Sea">Old Man and the Sea</option>
<option value="Where the Red Fern Grows">
Where the Red Fern Grows
</option>
<option value="Nineteen Eighty-Four">Nineteen Eighty-Four</option>
<option value="The Kite Runner">The Kite Runner</option>
</select>
</div>
);
}
}
export default TitleInput;
// DataModel.js
export const DataModel = {
userId: "",
id: "",
title: "",
body: ""
};
You can see the sandbox example here: https://codesandbox.io/embed/festive-engelbart-7ned7
<MTableEditRow
{...props}
data={this.state.newEntry}
// on the onEditingCanceled prop, you can access the cancel method
// in this instance, we're clearing the state and then calling the
// method provided by the prop to close the showAddRow, we're passing
// mode, which will return "add"
onEditingCanceled={(mode, rowData) => {
this.cancelAdd();
props.onEditingCanceled(mode);
}}
/>
Line 309, (onEditingCanceled): https://github.com/mbrn/material-table/blob/master/src/material-table.js
// index.js
import React, { Component } from "react";
import MaterialTable, { MTableEditRow } from "material-table";
import axios from "axios";
import DataModel from "./DataModel";
import TitleInput from "./TitleInput";
class Report extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
workOrderOptions: [],
newEntry: {
userId: "",
id: "",
title: "",
body: ""
}
};
this.handleNewTitle = this.handleNewTitle.bind(this);
this.cancelAdd = this.cancelAdd.bind(this);
}
renderData() {
const URL = "https://jsonplaceholder.typicode.com/posts";
axios
.get(URL)
.then(response => {
this.setState({
data: response.data
});
})
.catch(error => {
console.log("ERROR:", error);
});
}
// I want to fire this method upon canceling the "add row"
cancelAdd() {
this.setState({
newEntry: {
userId: "",
id: "",
title: "",
body: ""
}
});
}
handleNewTitle(title) {
this.setState({
newEntry: {
// ...this.state.newEntry.title,
title: title
}
});
}
componentDidMount() {
this.renderData();
}
render() {
const columns = [
{
title: "ID",
field: "id",
editable: "never"
},
{
title: "User ID",
field: "userId",
editable: "never"
},
{
title: "Title",
field: "title",
editable: "never"
},
{
title: "Body",
field: "body",
editable: "never"
}
];
if (this.state.data) {
return (
<div>
<MaterialTable
components={{
EditRow: props => {
return (
<div>
<TitleInput
value={this.state.newEntry.title}
title={this.handleNewTitle}
/>
{/* <BodyInput
value={this.state.newEntry.body}
body={this.handleNewBody}
/>, <UserIDInput />, etc... */}
<MTableEditRow
{...props}
data={this.state.newEntry}
// looks like there is with onEditingCanceled
onEditingCanceled={(mode, rowData) => {
this.cancelAdd();
props.onEditingCanceled(mode);
}}
/>
</div>
);
}
}}
editable={{
// Just a sample add
onRowAdd: newData =>
new Promise((resolve, reject) => {
const result = {
id: 15465,
userId: 87946542,
title: this.state.newEntry.title,
body: "Old man Santiago"
};
console.log(result);
const data = this.state.data;
data.push(result);
this.setState({
...this.state
});
resolve();
})
}}
data={this.state.data}
columns={columns}
title={"Title"}
/>
</div>
);
} else if (!this.state.data) {
return <div>Loading...</div>;
}
}
}
export default Report;
I am trying to pass values from a checkbox in a table in a child component to a parent component
As suggested here:
React: How to add an array of Ids to the state object from nested objects
My code is like this:
inner component:
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListPageTemplatesWithSelection extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/PageTemplates", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.Id,
Name: row.Name
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render(){
const columns = [
{
title: 'Id',
dataIndex: 'key',
key: 'key',
},
{
title: 'Name',
dataIndex: 'Name',
key: 'Name',
}
];
return (
<Table rowSelection={() => this.props.onRowSelect(this.columns.Id)} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListPageTemplatesWithSelection;
parent component
import React, { Component } from 'react';
import { Input} from 'antd';
import Form from '../../components/uielements/form';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
import ListPageTemplatesWithSelection from './ListPageTemplatesWithSelection';
const FormItem = Form.Item;
class CreateModernSiteCollectionForm extends Component {
constructor(props) {
super(props);
this.state = {Alias:'',DisplayName:'', Description:''};
this.handleChangeAlias = this.handleChangeAlias.bind(this);
this.handleChangeDisplayName = this.handleChangeDisplayName.bind(this);
this.handleChangeDescription = this.handleChangeDescription.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChangeAlias(event){
this.setState({Alias: event.target.value});
}
handleChangeDisplayName(event){
this.setState({DisplayName: event.target.value});
}
handleChangeDescription(event){
this.setState({Description: event.target.value});
}
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
let data = new FormData();
//Append files to form data
//data.append(
const options = {
method: 'post',
body: JSON.stringify(
{
"Alias": this.state.Alias,
"DisplayName": this.state.DisplayName,
"Description": this.state.Description
}),
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
adalApiFetch(fetch, "/SiteCollections", options)
.then(response =>{
if(response.status === 201){
Notification(
'success',
'Site collection created',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Site collection not created',
error
);
console.error(error);
});
}
});
}
render() {
// rowSelection object indicates the need for row selection
const handleRowSelect = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(selectedRowKeys);
}
};
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Alias" hasFeedback>
{getFieldDecorator('Alias', {
rules: [
{
required: true,
message: 'Please input your alias',
}
]
})(<Input name="alias" id="alias" onChange={this.handleChangeAlias} />)}
</FormItem>
<FormItem {...formItemLayout} label="Display Name" hasFeedback>
{getFieldDecorator('displayname', {
rules: [
{
required: true,
message: 'Please input your display name',
}
]
})(<Input name="displayname" id="displayname" onChange={this.handleChangeDisplayName} />)}
</FormItem>
<FormItem {...formItemLayout} label="Description" hasFeedback>
{getFieldDecorator('description', {
rules: [
{
required: true,
message: 'Please input your description',
}
],
})(<Input name="description" id="description" onChange={this.handleChangeDescription} />)}
</FormItem>
<ListPageTemplatesWithSelection onRowSelect={this.handleRowSelect} />
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
Create modern site
</Button>
</FormItem>
</Form>
);
}
}
const WrappedCreateModernSiteCollectionForm = Form.create()(CreateModernSiteCollectionForm);
export default WrappedCreateModernSiteCollectionForm;
However I get this error in the console
index.js:2177 Warning: Failed prop type: Invalid prop `rowSelection` of type `function` supplied to `Table`, expected `object`.
in Table (at ListPageTemplatesWithSelection.js:53)
in ListPageTemplatesWithSelection (at CreateModernSiteCollection.js:144)
in form (created by Form)
in Form (at CreateModernSiteCollection.js:112)
in CreateModernSiteCollectionForm (created by Form(CreateModernSiteCollectionForm))
in Form(CreateModernSiteCollectionForm) (at index.js:30)
According to ANTD docs, rowSelection is an object that serves as configuration for the table's row selection behavior. It looks like in your example you are trying to pass in a selection handler to your component. So according to the signature of this object, you should try to do
const rowSelection = {
onSelect: () => this.props.onRowSelect(this.columns.Id)
};
<Table rowSelection={rowSelection} />
I have a simple form with 3 inputs and one button.
The submit button is disabled by default, and each input has custom validation logic with regex.
How can I enable the button again when all validation passes?
This is my component:
import React, { Component } from 'react';
import { Input, Upload , Icon, message} from 'antd';
import Form from '../../components/uielements/form';
import Checkbox from '../../components/uielements/checkbox';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
const FormItem = Form.Item;
class RegisterTenantForm extends Component {
constructor(props) {
super(props);
this.state = {TenantId: '', TenantUrl: '', CertificatePassword: '', confirmDirty: false, loading: false, buttondisabled: true };
this.handleChangeTenantUrl = this.handleChangeTenantUrl.bind(this);
this.handleChangeCertificatePassword = this.handleChangeCertificatePassword.bind(this);
this.handleChangeTenantId= this.handleChangeTenantId.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleupload = this.handleupload.bind(this);
this.handleTenantIdValidation = this.handleTenantIdValidation.bind(this);
this.handleTenantAdminUrl = this.handleTenantAdminUrl.bind(this);
};
handleChangeTenantUrl(event){
this.setState({TenantUrl: event.target.value});
}
handleChangeCertificatePassword(event){
this.setState({CertificatePassword: event.target.value});
}
handleChangeTenantId(event){
this.setState({TenantId: event.target.value});
}
beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
if (!isJPG) {
message.error('You can only upload JPG file!');
}
}
handleupload(info){
//let files = e.target.files;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
this.setState({ loading: false });
this.setState({ 'selectedFile': info.file });
}
}
handleTenantIdValidation(rule, value, callback){
const form = this.props.form;
var re = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!form.getFieldValue('tenantid').match(re)) {
callback('Tenant id is not correctly formated id');
}
else {
callback();
}
}
handleTenantAdminUrl(rule, value, callback){
const form = this.props.form;
var re = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]#!\$&'\(\)\*\+,;=.]+$/i;
if (!form.getFieldValue('tenantadminurl').match(re)) {
callback('Tenant Url is not correctly formated id');
}
else {
callback();
}
}
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
/*Notification(
'success',
'Received values of form',
JSON.stringify(values)
);*/
let data = new FormData();
//Append files to form data
data.append("model", JSON.stringify({ "TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "CertificatePassword": this.state.CertificatePassword }));
//data.append("model", {"TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "TenantPassword": this.state.TenantPassword });
let files = this.state.selectedFile;
for (let i = 0; i < files.length; i++) {
data.append("file", files[i], files[i].name);
}
const options = {
method: 'put',
body: data,
config: {
headers: {
'Content-Type': 'multipart/form-data'
}
}
};
adalApiFetch(fetch, "/Tenant", options)
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ data: responseJson });
}
})
.catch(error => {
console.error(error);
});
}
});
}
render() {
const uploadButton = (
<div>
<Icon type={this.state.loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">Upload</div>
</div>
);
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Tenant Id" hasFeedback>
{getFieldDecorator('tenantid', {
rules: [
{
required: true,
message: 'Please input your tenant id',
},
{
validator: this.handleTenantIdValidation
}],
})(<Input name="tenantid" id="tenantid" onChange={this.handleChangeTenantId}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Certificate Password" hasFeedback>
{getFieldDecorator('certificatepassword', {
rules: [
{
required: true,
message: 'Please input your password!',
}
],
})(<Input type="password" name="certificatepassword" id="certificatepassword" onChange={this.handleChangeCertificatePassword}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Tenant admin url" hasFeedback>
{getFieldDecorator('tenantadminurl', {
rules: [
{
required: true,
message: 'Please input your tenant admin url!',
},
{
validator: this.handleTenantAdminUrl
}],
})(<Input name="tenantadminurl" id="tenantadminurl" onChange={this.handleChangeTenantUrl} />)}
</FormItem>
<FormItem {...formItemLayout} label="Certificate File">
<Upload onChange={this.handleupload} beforeUpload={this.beforeUpload}>
<Button >
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
</FormItem>
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit" disabled={this.state.buttondisabled}>
Register tenant
</Button>
</FormItem>
</Form>
);
}
}
const WrappedRegisterTenantForm = Form.create()(RegisterTenantForm);
export default WrappedRegisterTenantForm;
Update:
Answer should be provided using ant design API
In each validator if block, set buttonDisabledState state to true and in each else statement set it to false.
When all fields validations are passed then your btn disabled state will set to false.
Your button's disabled value is a boolean that comes from your component state.
So what you need to do is set that boolean to false when all checks have passed:
this.setState({buttonDisabled: false})
Add a boolean value to your state, i.e, isValidated that defaults to false and you change to true when all the validations pass. You're sorta already doing that with the state.buttondisabled - just setState that to false when the validations are all passing.
Have a look at this section, hopefully it will solve your problem
https://ant.design/components/form/#Form.Item
I am trying to use antd to create a form with a fileupload, but I cant make work the handleupload function, I have the error:
Cannot read property 'setState' of undefined
at handleupload (registertenantform.js:43)
The code is this:
import React, { Component } from 'react';
import { Input, Upload , Icon, message} from 'antd';
import Form from '../../components/uielements/form';
import Checkbox from '../../components/uielements/checkbox';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
const FormItem = Form.Item;
class RegisterTenantForm extends Component {
constructor(props) {
super(props);
this.state = {TenantId: '', TenantUrl: '', CertificatePassword: '' };
this.handleChangeTenantUrl = this.handleChangeTenantUrl.bind(this);
this.handleChangeCertificatePassword = this.handleChangeCertificatePassword.bind(this);
this.handleChangeTenantId= this.handleChangeTenantId.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChangeTenantUrl(event){
this.setState({TenantUrl: event.target.value});
}
handleChangeCertificatePassword(event){
this.setState({CertificatePassword: event.target.value});
}
handleChangeTenantId(event){
this.setState({TenantId: event.target.value});
}
beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
if (!isJPG) {
message.error('You can only upload JPG file!');
}
}
handleupload(info){
//let files = e.target.files;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
this.setState({ loading: false });
this.setState({ 'selectedFiles': info.file });
}
}
state = {
confirmDirty: false,
loading: false,
};
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
/*Notification(
'success',
'Received values of form',
JSON.stringify(values)
);*/
let data = new FormData();
//Append files to form data
data.append("model", JSON.stringify({ "TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "CertificatePassword": this.state.CertificatePassword }));
//data.append("model", {"TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "TenantPassword": this.state.TenantPassword });
let files = this.state.selectedFiles;
for (let i = 0; i < files.length; i++) {
data.append("file", files[i], files[i].name);
}
const options = {
method: 'put',
body: data,
config: {
headers: {
'Content-Type': 'multipart/form-data'
}
}
};
adalApiFetch(fetch, "/Tenant", options)
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ data: responseJson });
}
})
.catch(error => {
console.error(error);
});
}
});
}
render() {
const uploadButton = (
<div>
<Icon type={this.state.loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">Upload</div>
</div>
);
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Tenant Id" hasFeedback>
{getFieldDecorator('tenantid', {
rules: [
{
required: true,
message: 'Please input your tenant id',
},
],
})(<Input name="tenantid" id="tenantid" onChange={this.handleChangeTenantId}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Certificate Password" hasFeedback>
{getFieldDecorator('certificatepassword', {
rules: [
{
required: true,
message: 'Please input your password!',
}
],
})(<Input type="certificatepassword" onChange={this.handleChangeCertificatePassword}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Tenant admin url" hasFeedback>
{getFieldDecorator('tenantadminurl', {
rules: [
{
required: true,
message: 'Please input your tenant admin url!',
}
],
})(<Input type="tenantadminurl" onChange={this.handleChangeTenantUrl} />)}
</FormItem>
<FormItem {...tailFormItemLayout}>
<Upload onChange={this.handleupload} beforeUpload={this.beforeUpload}>
<Button>
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
<Button type="primary" htmlType="submit">
Register tenant
</Button>
</FormItem>
</Form>
);
}
}
const WrappedRegisterTenantForm = Form.create()(RegisterTenantForm);
export default WrappedRegisterTenantForm;
You missed to bind your handleupload function. Just add
this.handleupload = this.handleupload.bind(this)
in your RegisterTenantForm constructor
Or
you can rewrite a handleupload function using arrow function like so:
handleupload = (info) => {
//let files = e.target.files;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
// btw you dont need two separated setState here, you can do
// it in one setState
this.setState({
'selectedFiles': info.file,
loading: false
});
}
}
Your this is treating local scope inside promise as a result setState method should be undefined so you need to assign this into another variable like below:
const that = this;
and can access that into your promise code below:
adalApiFetch(fetch, "/Tenant", options)
.then(response => response.json())
.then(responseJson => {
if (!that.isCancelled) {
that.setState({
data: responseJson
});
}
})
.catch(error => {
console.error(error);
});