ANTD Table getColumnSearchProps broken with nested object - javascript

Using codes from the example: https://ant.design/components/table/#components-table-demo-custom-filter-panel
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, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: text =>
this.state.searchedColumn === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[this.state.searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
Error Uncaught TypeError: Cannot read property 'toString' of undefined thrown when trying to pass nested values in the ANTD Table:
<Table bordered size='small' dataSource={data} rowKey='_id'>
....
<Column
title='Name'
dataIndex={['profile', 'name']}
{...this.getColumnSearchProps(['profile', 'name'])}
/>
....
</Table>
Here is how the structure of the data (dataSource) for the table:
[
{_id: 'xxx1', profile : { name : 'username1' }, roles: ['xxx1']},
{_id: 'xxx2', profile : { name : 'username2' }, roles: ['xxx2']}
]
As per outlined in the documentation: https://ant.design/components/table/#Migrate-to-v4 :
Besides, the breaking change is changing dataIndex from nest string
path like user.age to string array path like ['user', 'age']. This
help to resolve developer should additional work on the field which
contains ..
hence the dataIndex={['profile', 'name']}, but this is not the same case for the getColumnSearchProps.
Anyone can help?

Since the dataIndex could be an array now, you need to take care of that case as well.
An example is provided below:
You basically have to
Replace
record[dataIndex]
with
get(record, dataIndex) // import get from "lodash.get";
and
this.state.searchedColumn === dataIndex
with
isequal(this.state.searchedColumn, dataIndex) // import isequal from "lodash.isequal";

Related

How to handle arrays in a Grommet DataTable?

I'm trying to use arrays in Grommet DataTable. My data looks like this :
{
customer: [
'BANANA',
'Banana',
'banana',
'republic of banana'
],
somethingelse: ['ABC','123','DEF']
}
In a regular Grommet Table , I'm able to use every cell by defining the first value from the array as title - for example customer[0] - and create an expandable arrow to show the rest of the data in 'customer' :
But I don't get how to do this on a cell basis for a Grommet DataTable ?
Here is the way I'm using it in the regular Grommet Table :
<TableCell scope="row" pad={{ left: '2px', righ: '3px' }}>
<TextInput name="tags" size="xsmall" />
</TableCell>
</TableRow>
{searchResults.length > 0 &&
searchResults.map((searchResult, index) => (
<TableRow key={index}>
<TableCell>
<Box direction="row">
<Text size="xsmall">{searchResult.customer[0]}</Text>
{searchResult.customer.length > 1 && (
<Button
plain
hoverIndicator={false}
icon={
isExpanded[index] ? (
<FormDown size="18px" />
) : (
<FormNext size="18px" />
)
}
onClick={() => toggleOpen(index)}
/>
)}
</Box>
<Box>
{isExpanded[index] && listElements(searchResult.customer)}
</Box>
</TableCell>
Here is my Form , using DataTable :
return (
<Form value={formData} onSubmit={onSubmit} onChange={onChange}>
...
<DataTable
fill
border={{ body: 'bottom' }}
paginate
columns={columns}
data={searchResults}
select={select}
onClickRow={(e) => console.log(e.datum)}
onSelect={() => {}}
step={8}
rowDetails={(row) => { // I'm able to use rowDetails to expand and display some data , but how can I use this to 1. Use the [0] element of the array as title and 2. apply to all cells in the row/table.
for (const cell in row) {
// if (cell.length > 1) {
// return listElements(cell);
// }
console.log(cell);
}
}}
...
/>
...
</Form>
);
I was able to achieve that by using the render function and passing a CellElement to it, in which I have created my rules :
const columns = [
{
property: 'customer',
header: <FormField label="Customer" name="customer" size="xsmall" />,
render: (datum) => <CellElement val={datum.customer} />,
},
CellElement.js
import { Box, Text, Button } from 'grommet';
import { FormNext, FormDown } from 'grommet-icons';
import React, { useState } from 'react';
const CellElement = ({ val }) => {
const title = Array.isArray(val) ? val[0] : val;
const [isExpanded, setIsExpanded] = useState({});
const toggleOpen = (category) => {
setIsExpanded({
...isExpanded,
[category]: !isExpanded[category],
});
};
const listElements = (arr) => {
return arr.slice(1).map((el, index) => (
<Text key={index} size="xsmall">
{el}
</Text>
));
};
return (
<Box>
<Box direction="row">
<Text size="xsmall">{title}</Text>
{Array.isArray(val) && val.length > 1 && (
<Button
plain
hoverIndicator={false}
icon={
isExpanded[title] ? (
<FormDown size="18px" />
) : (
<FormNext size="18px" />
)
}
onClick={() => toggleOpen(title)}
/>
)}
</Box>
<Box>{isExpanded[title] && listElements(val)}</Box>
</Box>
);
};
export default CellElement;

Uncaught TypeError: (intermediate value)(intermediate value)(intermediate value).map is not a function when rendering a react component

When I render an array of objects and try to render component inside map function is gives me this error:
Uncaught TypeError: (intermediate value)(intermediate value)(intermediate value).map is not a function
The problem occurs here:
{comments.map((obj, i) => (
<CommentsBlock
key={i}
items={{
user: {
fullName: obj?.user?.fullName,
avatarUrl: obj?.user?.avatarUrl,
},
text: obj?.text,
}}
isLoading={false}
id={obj._id}
isEditable={userData._id == obj.user._id}
><Index /></CommentsBlock>
))}
CommentBlock component:
export const CommentsBlock = ({
id,
items,
children,
isLoading = true,
isEditable = false,
}) => {
const dispatch = useDispatch();
const onClickRemove = () => {
if (window.confirm("Вы действительно хотите удалить статью?")) {
//dispatch(fetchRemovePosts(id));
}
};
return (
<SideBlock title="Комментарии">
<List>
{(isLoading ? [...Array(5)] : items).map((obj, index) => (
<React.Fragment key={index}>
<ListItem alignItems="flex-start">
<ListItemAvatar>
{isLoading ? (
<Skeleton variant="circular" width={40} height={40} />
) : (
<Avatar alt={obj.user.fullName} src={obj.user.avatarUrl} />
)}
</ListItemAvatar>
{isLoading ? (
<div style={{ display: "flex", flexDirection: "column" }}>
<Skeleton variant="text" height={25} width={120} />
<Skeleton variant="text" height={18} width={230} />
</div>
) : (
<ListItemText
primary={
<Typography
style={{
display: "flex",
justifyContent: "space-between",
}}
>
{obj.user.fullName}
{isEditable ? (
<IconButton color="secondary" style={{ padding: 0 }}>
<DeleteIcon />
</IconButton>
) : (
""
)}
</Typography>
}
secondary={obj.text}
/>
)}
</ListItem>
<Divider variant="inset" component="li" />
</React.Fragment>
))}
</List>
{children}
</SideBlock>
);
};
array comment comes from here
const comments = useSelector((state) => state.comment.data);
that's how comments looks like
So i expect to render these elements
(isLoading ? [...Array(5)] : items).map((obj, index)
This line looks very suspicious. You receive loading as prop and set true, and if loading is true [...Array(5)] will be
[undefined, undefined, undefined, undefined, undefined]
Usually, you want to display some loader, if the loading is true.
And then below you use obj.user.fullName, but you map over an array with 5 undefineds, so you get
undefined.user.fullName
This is what I can tell from the code, if you add the code to some sandbox and share, I may help more, I don't see the full picture.
You're passing items as object thru CommentsBlock component props, but in List component you're trying to access as array here: {(isLoading ? [...Array(5)] : items).map((obj, index) => ( which is the reason behind error. Can not run map on object.
So either pass the items as arrau or just access as object directly.
[...Array(5)] is producing array with empty value (undefined) where items is object which not iterable thu map.
Thanks.

How to make first dropdown not render in reactjs and ant design

I need to make my current draggable component's dropdown to not render on specific cell (specifically the first Row). However, it will still show the cell just without the dropdown. I tried to search for a way however could not find any. Appreciate any help / directions given. Thanks! I also also attached the screenshot of current and expected. (Just take note of the dropdown being empty)
*Current Interface - all dropdown rendered
*Expected New Interface - first dropdown not rendered
Main code with ant design component Row/Col and Select/Option for the dropdown:
render() {
return (
<div>
<Table
scroll={{ x: 300 }}
pagination={{
defaultPageSize: 10,
showSizeChanger: true,
pageSizeOptions: ['10', '25', '50'],
}}
columns={this.state.columns}
dataSource={this.state.datas}
></Table>
<Modal
destroyOnClose={true}
width={'80%'}
title='Approval Sequence'
visible={this.state.isVisible}
onOk={() => {
updateFlowType(this.state.currentItem.id, {
name: this.state.currentItem.name,
sites: this.state.initTags.map((x) => ({
idSite: x.id,
returnToStep: x.returnToStep,
})),
})
.then((r) => {
notification['sucess']({
message: 'Success',
description: 'Save data success',
});
this.setState({ isVisible: false });
this.getData();
})
.catch(() => {
notification['failed']({
message: 'Failed',
description: 'Failed to save',
});
});
}}
onCancel={() => {
this.setState({ isVisible: false });
}}
>
<Row gutter={[2, 2]}>
<Col style={{ textAlign: 'center' }} span={8}>
<Text strong>Role</Text>
</Col>
<Col style={{ textAlign: 'center' }} span={8}>
<Text strong> Return to Department: </Text>
</Col>
</Row>
<div className='list-area'>
<div className='drag-list'>
<DraggableArea
onChange={(initTags) => this.setState({ initTags })}
isList
tags={this.state.initTags}
render={({ tag }) => (
<Row>
<Col span={8}>
<Button style={{ width: '100%' }}>{tag.name}</Button>
</Col>
<Col span={16}>
<Select
onChange={(e) => {
//create clone
let clone = [...this.state.initTags];
let item = clone.find((x) => x.id === tag.id);
let itemReject = clone.find((x) => x.name === e);
console.log('itemReject', itemReject);
//create returnToStep in item
item.returnToStep = itemReject.id;
this.setState({
initTags: clone,
});
}}
//placeholder = 'Select return step'
style={{ width: '100%' }}
>
{this.getReject(tag.name).map((newTag) => (
//getReject function will slice to get only items before the current iteration object (e.g. if current is index 3, only get index 0~2)
<Option key={newTag.name} value={newTag.name}>
{newTag.name}
</Option>
))}
</Select>
</Col>
</Row>
)}
></DraggableArea>
</div>
</div>
</Modal>
</div>
);
}
You can do "conditional render" using a condition and a component like this:
const someBoolean = true;
retrun (
{ someBoolean && <SomeComponent /> }
)
if someBoolean is true, then <SomeComponent/> will show.
if someBoolean is false, then <SomeComponent/> will not show.
So, just use that to conditionally render your dropdown column or any other component.
If, the table rows are based upon content and dynamically rendered, then I would modify the content accordingly. (i.e. don't provide the rows you don't want to render).

how to make a custom and reusable table component in typescript react?

i want to make a component of table which is reusable for every function
i am facing issue regrading columns and rows
how can i pass every time different data and columns for every table ?
currently i have created component for each table that's not i want to do
if i have the similar table how can i pass different data and columns to another ?
<Table
className="v-table m-table"
// rowClassName={(record, index) => record.type}
scroll={{ x: "max-content", y: 600 }}
columns={mColumns}
dataSource={mData}
pagination={{
className: "list-pagination",
total: 85,
defaultPageSize: 10,
defaultCurrent: 1,
}}
expandable={{
expandedRowRender,
rowExpandable: (record) => record.name !== "Not Expandable",
expandIcon: ({ expanded, onExpand, record }) =>
expanded ? (
<div className="icon-wrapper">
<i
className="icon-table-arrow-down"
style={{
fontSize: "9px",
width: "16px",
cursor: "pointer",
}}
onClick={(e) => onExpand(record, e)}
/>
</div>
) : (
<div className="icon-wrapper">
<i
className="icon-table-arrow-right"
style={{ width: "16px", cursor: "pointer" }}
onClick={(e) => onExpand(record, e)}
/>
</div>
),
}}
/>
I'm not sure I understand the question... why you not simply try:
const MyTable = ({missionColumns, missionData}) => {
return <Table
className="vmsb-table mission-table"
// rowClassName={(record, index) => record.type}
scroll={{ x: "max-content", y: 600 }}
columns={missionColumns}
dataSource={missionData}
...etc...
</Table>
}
Usage:
<MyTable missionColumns={yourcolumns} missionData={yourdata} />
Regarding your second question, you can pass in the same way a style attribute, es:
const YourComponent = ({displayValue}) => {
return <div style={{display:displayValue}} />
}
usage:
<YourComponent displayValue="none" />
or if you want to pass the entire style:
const YourComponent = ({style}) => {
return <div style={{...style}} />
}
usage:
<YourComponent style={{display:'none', margin:'4px' etc...}} />

Show only an attribute, but save entire object (React Final Form + Downshift)

this problem may have already been solved but the examples that I found did not help me much.
downshift version:6.1.0
node version:14.15.4
npm version:6.14.10
react version: 17.0.1
What you did:
Tried to show an object attribute on input field, but i want save entire object
What happened:
The object is saved well but I can't show only one property in the input
<Field
name={`${name}.product`}
items={productList}
index={index}
component={DownShiftInput}
placeholder="Name"
/>;
const itemToString = item => {
return item ? item : '';
};
const DownShiftInput = ({
input,
meta,
placeholder,
items,
index,
...rest
}) => (
<Control name={placeholder} my={4}>
<FormLabel htmlFor={placeholder}>{placeholder}</FormLabel>
<Downshift
{...input}
onInputValueChange={inputValue => {
input.onChange(inputValue);
}}
itemToString={itemToString}
selectedItem={input.value}
>
{({
getInputProps,
getItemProps,
getLabelProps,
isOpen,
inputValue,
highlightedIndex,
selectedItem,
}) => {
const filteredItems = matchSorter(items, inputValue, {
keys: ['name'],
maxRanking: matchSorter.rankings.STARTS_WITH,
});
return (
<div className="downshift" style={{ position: 'relative' }}>
<Input
{...getInputProps({
name: input.name,
placeholder,
})}
/>
{isOpen && !!filteredItems.length && (
<div
className="downshift-options"
style={{
background: 'white',
position: 'absolute',
top: '100%',
left: 15,
right: 0,
zIndex: 4,
}}
>
{filteredItems.map((item, index) => {
return (
<div
{...getItemProps({
key: item.id,
index,
item,
style: {
backgroundColor:
highlightedIndex === index ? 'lightgray' : 'white',
fontWeight: selectedItem === item ? 'bold' : 'normal',
},
})}
>
{item.name}
</div>
);
})}
</div>
)}
</div>
);
}}
</Downshift>
<Error name={placeholder} />
</Control>
);
Thanks!
The solution for the user lcordier42 on downshift github:
<Input {...getInputProps({ name: input.name, placeholder, value: selectedItem.name, })} />

Categories