19

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;
nnnmmm
  • 7,964
  • 4
  • 22
  • 41
Luis Valencia
  • 32,619
  • 93
  • 286
  • 506
  • Can you please give codepen project? – Maheer Ali Mar 10 '19 at 14:53
  • I can not see any state : any; in your code. is that implemented in driven class? you have to look for where you defined 'state' and there you can found out the problem. – Mazdak Mar 10 '19 at 15:00
  • I dont know what is codepend and it wont probably run becaues it has server side api with authentication – Luis Valencia Mar 10 '19 at 15:09
  • in `src/helpers/AsyncFunc.js:26 23 | const { default: Component } = await importComponent();` is this actually undefined? some recursive imports that errored? – Estradiaz Mar 17 '19 at 12:10

2 Answers2

11

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.

mehamasum
  • 5,554
  • 2
  • 21
  • 30
  • the highlighter its another component which highlights the text you search for, its like that in the antd documentation I think – Luis Valencia Mar 09 '19 at 12:08
  • I see. You could try removing the render method of `getColumnSearchProps()` since you are providing your own render method later and see it that works. – mehamasum Mar 09 '19 at 13:46
  • I removed the render from the second place, and modified the onfilter as shown above, but still get the same error https://screencast.com/t/UZkm6bRBq – Luis Valencia Mar 09 '19 at 17:40
  • I reproduced your code [here](https://codesandbox.io/s/o9zm575lvz?fontsize=14) and it works fine. Looks like the problem could be elsewhere. If you can add a [MCVE](https://stackoverflow.com/help/mcve) of the other parts that are relevant to the error (for example `AsyncFunc.js`), it might be helpful. – mehamasum Mar 10 '19 at 20:00
  • I will update the question here later, basically that component is embedded within another component I wrote too, the rest of the component chain in the upper levels are from the template, so I expect I dont have to change anything on the template components, I believe the AsyncFunc.cs is not my code, but I will check later and update here – Luis Valencia Mar 11 '19 at 13:34
  • I have updated the question with the container component – Luis Valencia Mar 11 '19 at 15:15
1

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

Fraction
  • 11,668
  • 5
  • 28
  • 48