I have an antd table with 2 columns which I need to filter on the first, and search text on the second column.
From my code, the application is rendered fine. Please note the tags field is a json array, not a text field, so I guess that has something to do with the error.
Updated 1 Code.
import React, { Component } from 'react';
import { Table, Tag, Button, Icon, Input} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import Highlighter from 'react-highlight-words';
class ListPageTemplatesWithSelection extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
filteredInfo: null,
sortedInfo: null,
searchText: ''
};
this.handleChange= this.handleChange.bind(this);
this.clearFilters= this.clearFilters.bind(this);
this.clearAll= this.clearAll.bind(this);
this.getColumnSearchProps= this.getColumnSearchProps.bind(this);
this.handleSearch= this.handleSearch.bind(this);
this.handleReset= this.handleReset.bind(this);
}
handleSearch (selectedKeys, confirm){
confirm();
this.setState({ searchText: selectedKeys[0] });
}
handleReset(clearFilters){
clearFilters();
this.setState({ searchText: '' });
}
getColumnSearchProps = (dataIndex) => ({
filterDropdown: ({
setSelectedKeys, selectedKeys, confirm, clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
ref={node => { this.searchInput = node; }}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm)}
icon="search"
size="small"
style={{ width: 90, marginRight: 8 }}
>
Search
</Button>
<Button
onClick={() => this.handleReset(clearFilters)}
size="small"
style={{ width: 90 }}
>
Reset
</Button>
</div>
),
filterIcon: filtered => <Icon type="search" style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex]
? record[dataIndex]
.toString()
.toLowerCase()
.includes(value.toLowerCase())
: false,
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
}
})
handleChange(pagination, filters, sorter){
console.log('Various parameters', pagination, filters, sorter);
this.setState({
filteredInfo: filters,
sortedInfo: sorter,
});
}
clearFilters(){
this.setState({ filteredInfo: null });
}
clearAll(){
this.setState({
filteredInfo: null,
sortedInfo: null,
});
}
fetchData = () => {
adalApiFetch(fetch, "/PageTemplates", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.Id,
Name: row.Name,
SiteType: row.SiteType,
Tags: row.Tags
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render(){
let { sortedInfo, filteredInfo } = this.state;
sortedInfo = sortedInfo || {};
filteredInfo = filteredInfo || {};
const columns = [
{
title: 'Id',
dataIndex: 'key',
key: 'key',
},
{
title: 'Name',
dataIndex: 'Name',
key: 'Name',
},
{
title: 'Site Type',
dataIndex: 'SiteType',
key: 'SiteType',
filters: [
{ text: 'Modern Team Site', value: 'Modern Team Site' },
{ text: 'CommunicationSite', value: 'CommunicationSite' },
],
filteredValue: filteredInfo.SiteType || null,
onFilter: (value, record) => record.SiteType.includes(value),
},{
title: 'Tags',
key: 'Tags',
dataIndex: 'Tags',
...this.getColumnSearchProps('Tags'),
render: Tags => (
<span>
{Tags && Tags.map(tag => {
let color = tag.length > 5 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return <Tag color={color} key={tag}>{tag.toUpperCase()}</Tag>;
})}
</span>)
}
];
const rowSelection = {
selectedRowKeys: this.props.selectedRows,
onChange: (selectedRowKeys) => {
this.props.onRowSelect(selectedRowKeys);
}
};
return (
<div>
<Button onClick={this.clearFilters}>Clear filters</Button>
<Button onClick={this.clearAll}>Clear filters and sorters</Button>
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} onChange={this.handleChange} />
</div>
);
}
}
export default ListPageTemplatesWithSelection;
However when I add this line:
...this.getColumnSearchProps('Tags'),
Then I get this error
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
▶ 23 stack frames were collapsed.
AsyncFunc._callee$
src/helpers/AsyncFunc.js:26
23 | const { default: Component } = await importComponent();
24 | Nprogress.done();
25 | if (this.mounted) {
> 26 | this.setState({
27 | component: <Component {...this.props} />
28 | });
29 | }
Update 2
This is the container 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 CreateCommunicationSiteCollectionForm extends Component {
constructor(props) {
super(props);
this.state = {Title:'',Url:'', SiteDesign:'', Description:'',Owner:'',Lcid:'', PageTemplateIds : []};
this.handleChangeTitle = this.handleChangeTitle.bind(this);
this.handleValidationCommunicationSiteUrl = this.handleValidationCommunicationSiteUrl.bind(this);
this.handleChangeCommunicationSiteUrl = this.handleChangeCommunicationSiteUrl.bind(this);
this.handleChangeSiteDesign = this.handleChangeSiteDesign.bind(this);
this.handleChangeDescription = this.handleChangeDescription.bind(this);
this.handleChangeOwner = this.handleChangeOwner.bind(this);
this.handleChangelcid = this.handleChangelcid.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRowSelect = this.handleRowSelect.bind(this);
}
handleRowSelect(ids) {
this.setState({ PageTemplateIds: ids });
}
handleChangeTitle(event){
this.setState({Title: event.target.value});
}
handleValidationCommunicationSiteUrl(rule, value, callback){
const form = this.props.form;
const str = form.getFieldValue('communicationsiteurl');
var re = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/i;
if (str && !str.match(re)) {
callback('Communication site url is not correctly formated.');
}
else {
callback();
}
}
handleChangeCommunicationSiteUrl(event){
this.setState({Url: event.target.value});
}
handleChangeSiteDesign(event){
this.setState({SiteDesign: event.target.value});
}
handleChangeDescription(event){
this.setState({Description: event.target.value});
}
handleChangeOwner(event){
this.setState({Owner: event.target.value});
}
handleChangelcid(event){
this.setState({Lcid: 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(
{
"Title": this.state.Title,
"Url": this.state.Url,
"SiteDesign": this.state.SiteDesign,
"Description": this.state.Description,
"Owner": this.state.Owner,
"Lcid": this.state.Lcid,
"PageTemplateIds": this.state.PageTemplateIds
}),
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
adalApiFetch(fetch, "/SiteCollection/CreateCommunicationSite", options)
.then(response =>{
if(response.status === 201){
Notification(
'success',
'Communication Site created',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Site collection not created',
error
);
console.error(error);
});
}
});
}
render() {
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="Title" hasFeedback>
{getFieldDecorator('Title', {
rules: [
{
required: true,
message: 'Please input your communication site title',
}
]
})(<Input name="title" id="title" onChange={this.handleChangeTitle} />)}
</FormItem>
<FormItem {...formItemLayout} label="Communication Site Url" hasFeedback>
{getFieldDecorator('communicationSiteUrl', {
rules: [
{
required: true,
message: 'CommunicationSite site collection url',
},
{
validator: this.handleValidationCommunicationSiteUrl
}
]
})(<Input name="communicationsSiteUrl" id="communicationsSiteUrl" onChange={this.handleChangeCommunicationSiteUrl} />)}
</FormItem>
<FormItem {...formItemLayout} label="Site Design" hasFeedback>
{getFieldDecorator('sitedesign', {
rules: [
{
required: true,
message: 'Please input your site design',
}
]
})(<Input name="sitedesign" id="sitedesign" onChange={this.handleChangeSiteDesign} />)}
</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>
<FormItem {...formItemLayout} label="Owner" hasFeedback>
{getFieldDecorator('owner', {
rules: [
{
required: true,
message: 'Please input your owner',
}
],
})(<Input name="owner" id="owner" onChange={this.handleChangeOwner} />)}
</FormItem>
<FormItem {...formItemLayout} label="Lcid" hasFeedback>
{getFieldDecorator('lcid', {
rules: [
{
required: true,
message: 'Please input your lcid',
}
],
})(<Input name="lcid" id="lcid" onChange={this.handleChangelcid} />)}
</FormItem>
<ListPageTemplatesWithSelection onRowSelect={this.handleRowSelect} selectedRows={this.state.PageTemplateIds}/>
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
Create communication site
</Button>
</FormItem>
</Form>
);
}
}
const WrappedCreateCommunicationSiteCollectionForm = Form.create()(CreateCommunicationSiteCollectionForm);
export default WrappedCreateCommunicationSiteCollectionForm;
It works fine if user only uses the filter functionality on one column, but it becomes annoying when the user wants to filter multiple columns. It would be very handy if there is a filterTextInput below the column title that allows users to type directly. What does the proposed API look like?
The main difference between front-end filtering and back-end filtering is whether it is back-end paging. If it is a back-end page, the front-end page can only filter the data of the current page, while the back-end page filters the qualified data in all data.
Currently, there is a filterDropdown that allows developers to build a custom filter panel, so it takes the user one click per column to filter. It works fine if user only uses the filter functionality on one column, but it becomes annoying when the user wants to filter multiple columns.
I use React and use the Antd (Ant Design) dependency. React version is 16.9.0 and Antd version is 3.23.1. I use the reaction hooks to create the site.
It is very difficult to guess what went wrong from the error you provided. So the best I can do is to point out a few things that you should take care of.
The render method of getColumnSearchProps()
is erroneous. If the text is null
(due to a row not having any Tags) it will try to convert it to a string and crash. To avoid that, check text
exists before rendering:
render: text =>
text ? (
<Highlighter
highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
searchWords={[this.state.searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : null
The same applies for onFilter
method:
onFilter: (value, record) =>
record[dataIndex]
? record[dataIndex]
.toString()
.toLowerCase()
.includes(value.toLowerCase())
: false,
You have two render
methods for rendering Tags column. One inside getColumnSearchProps()
and one after ...this.getColumnSearchProps('Tags')
call. This should be fine because the later will override the previous. Then again, why would you declare the precious if you don't need it?
Hope this helps.
As said in another thread, this error can rise if you try to import a non-existent component. Make sure you have no typo and that the component indeed named that way. In case of libraries make sure you use the proper version, since components can have different names in different versions.
Links with similar issue:
Uncaught Error: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function but got: object
React.createElement: type is invalid -- expected a string or a class/function but got: undefined
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With