How to use Radio groups inside Antd table? - antd

I want to do this: each row is a Radio group, each cell is a Radio button, like the picture:
An example of Radio group is like:
<Radio.Group onChange={this.onChange} value={this.state.value}>
<Radio value={1}>A</Radio>
<Radio value={2}>B</Radio>
<Radio value={3}>C</Radio>
<Radio value={4}>D</Radio>
</Radio.Group>
But I don't know how to add a Radio group to wrap each Antd table row?
My current code is:
renderTable() {
let columns = [];
columns.push(
{
title: '',
dataIndex: 'name',
key: 'name',
width: '45vw',
},
);
this.props.task.options.forEach((option, i) => {
columns.push(
{
title: option,
dataIndex: option,
key: option,
className: 'choice-table-column',
render: x => {
return <Radio value={0} />
},
},
);
});
let rowHeaders = [];
this.props.task.extras.forEach((extra, i) => {
rowHeaders.push(
{"name": `${i + 1}. ${extra}`},
);
});
// How can I pass a className to the Header of a Table in antd / Ant Design?
// https://stackoverflow.com/questions/51794977/how-can-i-pass-a-classname-to-the-header-of-a-table-in-antd-ant-design
const tableStyle = css({
'& thead > tr > th': {
textAlign: 'center',
},
'& tbody > tr > td': {
textAlign: 'center',
},
'& tbody > tr > td:first-child': {
textAlign: 'left',
},
});
return (
<div>
<Table className={tableStyle} columns={columns} dataSource={rowHeaders} size="middle" bordered pagination={false} />
</div>
);
}

I don't think it is possible to use radio group for each row, however you can achieve it in a traditional way.
Here is code sample
https://codesandbox.io/s/goofy-benz-12kv5
class App extends React.Component {
state = {
task: { options: [1, 2, 3, 4, 5], extras: [6, 7, 8, 9, 10] },
selected: {}
};
onRadioChange = e => {
let name = e.currentTarget.name;
let value = e.currentTarget.value;
this.setState({
...this.state,
selected: { ...this.state.selected, [name]: value }
});
};
onSubmit = () => {
console.log(this.state.selected);
this.setState({
...this.state,
selected: {}
});
};
render() {
let columns = [];
columns.push({
title: "",
dataIndex: "name",
key: "name",
width: "45vw"
});
this.state.task.options.forEach((option, i) => {
columns.push({
title: option,
key: option,
render: row => {
return (
<input
type="radio"
checked={this.state.selected[row.name] == option}
onChange={this.onRadioChange}
name={row.name}
value={option}
/>
);
}
});
});
let rowHeaders = [];
this.state.task.extras.forEach((extra, i) => {
rowHeaders.push({ name: `${i + 1}.${extra}` });
});
return (
<div>
<Button onClick={this.onSubmit} type="primary">
{" "}
Submit
</Button>
<Table
columns={columns}
dataSource={rowHeaders}
size="middle"
bordered
pagination={false}
/>
<Tag color="red">Selected options</Tag>
<br />
{JSON.stringify(this.state.selected)}
</div>
);
}
}

hi there i had the same problem and base on new updates on antd this way of using is easier
<Table
rowSelection={{
type: "radio",
getCheckboxProps: (record) => {
console.log("record", record);
},
}}
pagination={{ hideOnSinglePage: true }}
columns={columns}
dataSource={data}
/>
example : https://ant.design/components/table/#components-table-demo-row-selection
for hiding table header : https://newbedev.com/javascript-antd-table-hide-table-header-code-example
hope its usefull

Related

How to handle two api calls in react-typescript ant-design

interface SingleCustomerShipmentData {
_id: string;
shipping_type: string;
origin_port_city: string;
destination_port_city: string;
shipment_pickup_date: string;
}
interface SingleCustomerData {
id: string;
first_name: string;
last_name: string;
email: string;
phone: string;
Avatar: string;
}
const columns: ColumnsType<SingleCustomerShipmentData> = [
{
title: 'Shipment Type',
dataIndex: 'shipping_type',
key: 'shipping_type',
render: (shipping_type, data) => {
return (
<div>
<img className='customers-avatar' src={data.Avatar} alt="" />
{shipping_type}
</div>
)
},
},
{
title: 'Origin',
dataIndex: 'origin_port_city',
key: 'origin_port_city',
},
{
title: 'Destination',
dataIndex: 'destination_port_city',
key: 'destination_port_city',
},
{
title: 'Shipment Date',
dataIndex: 'shipment_pickup_date',
key: 'shipment_pickup_date',
},
{
title: 'Shipping ID',
dataIndex: '_id',
key: '_id',
},
{
title: '',
key: 'action',
render: () => {
return (
<Space size="middle"
<Link to="/shipment-details" className='space-action-green'>View Details</Link>
</Space>
)
},
},
];
const Shipments: React.FC = () => {
const [shipmentData, setShipmentData] = useState(null);
const [customersData, setCustomersData] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
getSingleCustomer()
}, [])
useEffect(() => {
getSingleCustomerShipment()
}, [])
const getSingleCustomer = () => {
setLoading(true);
return makeAPICall({
path: `get_single_customer/123456789`,
method: "GET",
})
.then((data) => {
setCustomersData(data);
setLoading(false);
console.log(data);
})
.catch((err) => {
setLoading(false);
console.log(err);
});
}
const getSingleCustomerShipment = () => {
setLoading(true);
return makeAPICall({
path: `get_single_customer_shipments/123456789`,
method: "GET"
}).then((data) => {
setShipmentData(data);
console.log(data);
}).catch((err) => {
setLoading(false);
console.log(err);
})
}
return (
<SiderLayout>
<div className="site-layout-background" style={{ padding: "40px 40px", minHeight: 710 }}>
<Link className='shipment-back' to="/">
<img src={ArrowLeft} alt="" />
</Link>
{loading ? "loading..." : (
<>
<div className='shipment__container'>
<div className='shipment__container--image'>
<img src={data.Avatar} alt="" style={{ marginRight: 10 }} />
<div>
<p className='bold'>{first_name}</p>
<p>{email}</p>
<p>{phone}</p>
</div>
</div>
<div>
<span className='shipment__container--edit'>Edit</span>
</div>
</div>
<div className='shipment__border'>
<div className='shipment-spacing'>
<button className='new-shipment'>Add New Shipment {" "} +</button>
<select className='shipment-select' name="" id="">
<option value="">Shipment Type</option>
</select>
<select className='shipment-select' name="" id="">
<option value="">Shipment Date</option>
</select>
</div>
<div className='shipment-search'>
<form className="nosubmit">
<input className="nosubmit" type="search" placeholder="Search by shipment ID, Destination" />
</form>
</div>
</div>
<Table columns={columns} dataSource={data} />
</>
)}
</div>
</SiderLayout>
So I'm having these two api calls to be made on the same page. However I created two interface because both would definitely be needed on the it. But I don't really know how I can achieve that, because it is given an error message cannot find name data. So I brought it here for any person with an idea of it to help me out.
const [shipmentData, setShipmentData] = useState(null);
const [customersData, setCustomersData] = useState(null);
these shouldn't start as a null value. Because AntD Table requires an array to render.
const [shipmentData, setShipmentData] = useState([]);
const [customersData, setCustomersData] = useState([]);
should solve the problem you are facing.

Conditional row format not updating

I am building an app in Ag-grid React
I would like the grid to highlight a row if the user has tagged it by clicking on a checkbox. I am using rowClassRules, and it works fine: if the user edits the value of the tag field for a row from false to true, the row becomes highlighted
When I add in a cell renderer to make the tag field a checkbox it stops working, see code below
Any advice on what I am doing wrong would be appreciated
index.js
import React, { useState } from "react";
import { render } from "react-dom";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
import "./index.css"
const App = () => {
const AgGridCheckbox = (props) => {
const boolValue = props.value && props.value.toString() === "true";
const [isChecked, setIsChecked] = useState(boolValue);
const onChanged = () => {
props.setValue(!isChecked);
setIsChecked(!isChecked);
};
return (
<div>
<input
type="checkbox"
checked={isChecked}
onChange={onChanged}
/>
</div>
);
};
const [rowData] = useState([
{ tag: true, make: "Toyota", model: "Celica", price: 35000 },
{ tag: false, make: "Ford", model: "Mondeo", price: 32000 },
{ tag: false, make: "Porsche", model: "Boxter", price: 72000 },
]);
const [columnDefs] = useState([
{ field: "tag", cellRenderer: AgGridCheckbox },
// { field: "tag", editable: true },
{ field: "make" },
{ field: "model" },
{ field: "price" },
]);
const gridOptions = {
rowClassRules: {
"row-tagged": (params) => params.api.getValue("tag", params.node),
},
};
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 800 }}>
<AgGridReact
gridOptions={gridOptions}
rowData={rowData}
columnDefs={columnDefs}
></AgGridReact>
</div>
);
};
render(<App />, document.getElementById("root"));
index.css
.row-tagged {
background-color: #91bd80 !important;
}
I've done some more research and if I add redrawRows() to the onChanged() handler in the cell renderer thus:
const onChanged = () => {
props.setValue(!isChecked);
setIsChecked(!isChecked);
setRowData(rowData);
console.log(props);
props.api.redrawRows({ rowNodes: [props.node] });
};
It now works. Note that passing { rowNodes: [props.node] } means (I assume) that it only redraws a single row.
Supplementary Question: Is this the right way to go? Is there a more efficient way?

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())
)
);
}
});

Gutenberg Block Development: Only one RichText content is saving

I added two RichText components in my block.
registerBlockType( 'hallmark/gray-content-container', {
title: __( 'Gray Content Container' ),
icon: 'grid-view',
category: 'hallmark-blocks',
keywords: [
__( 'Hallmark gray content' ),
__( 'Hallmark' ),
__( 'Gray content container' ),
],
attributes:{
contentHeading: {
type: 'string',
source: 'children',
selector: 'h1,h2,h3,h4,h5,h6'
},
textContent: {
type: 'string'
}
},
edit: function( props ) {
var textContent = props.attributes.textContent;
var contentHeading = props.attributes.contentHeading;
function onChangeTextContent( content ) {
props.setAttributes( { textContent: content } );
}
function onChangeHeading (heading) {
props.setAttributes( { contentHeading: heading} );
}
return (
<div className={ props.className }>
<label className="editor-content-section-label">Content for gray section</label>
<RichText
tagName="h1"
value={contentHeading}
onChange={onChangeHeading}
placeholder={ __( 'Add a heading' ) }
keepPlaceholderOnFocus
/>
<RichText
tagName="p"
className={props.className}
onChange={onChangeTextContent}
value={textContent}
placeholder={ __( 'Add content' ) }
keepPlaceholderOnFocus
/>
</div>
);
},
save: function( props ) {
//return null;
return(
<div className={props.className}>
<div className="gray-bg">
<div className="constrain content">
<RichText.Content tagName="h1" value={ attributes.contentHeading } />
<RichText.Content tagName="p" value={ attributes.textContent } />
</div>
</div>
</div>
);
},
} );
I tried two different approaches to save the data.
Using default save() function
save: function( props ) {
return(
<div className={props.className}>
<div className="gray-bg">
<div className="constrain content">
<RichText.Content tagName="h1" value={ attributes.contentHeading } />
<RichText.Content tagName="p" value={ attributes.textContent } />
</div>
</div>
</div>
);
},
Saving it in PHP:
Using render_callback method (Using return null; from block's default save() function.
register_block_type( 'hallmark/white-content-container', array(
'render_callback' => 'hall_render_white_content'
) );
function hall_render_white_content( $atts ) {
$heading = $atts['contentHeading'];
$raw_content = $atts['textContent'];
$full_content = $heading . $raw_content;
// var_dump($full_content);
$content = hall_clean_shortcode_block_content( $full_content );
return '<div class="gray-bg"><div class="constrain content">' . $content . '</div></div>';
}
atts['contentHeading'] element does not exist at all in $atts array. When I check var_dump( $attas ); it has textContentelement present.
The problem is both approaches are only saving the textContent. contentHeading is not at all saving.
What I am missing?
Try setting
attributes:{
contentHeading: {
type: 'string',
source: 'children',
selector: 'h1'
},
textContent: {
type: 'string'
selector: 'p'
}
},
I think the selectors have to exactly match what is set in the save method.
<div className="constrain content">
<RichText.Content tagName="h1" value={ attributes.contentHeading } />
<RichText.Content tagName="p" value={ attributes.textContent } />
</div>
I think you also need a unique selector, so if you had two RichText paragraphs, you could do
textContentA: {
type: 'string'
selector: 'p.content-a'
}
textContentB: {
type: 'string'
selector: 'p.content-b'
}
For debugging use console.log(props.attributes) inside your edit function and observe if the values of contentHeading is changing or not when you edit. edit() function will be called each time if the state or props of component changes. As per my lucky guess the source of contentHeading should be 'text' instead of children.

type is invalid: expected a string (for built-in components) or a class/function in custom react component

I have an antd table with 2 columns which I need to filter on the first, and search text on the second column.
If I remove the line: ...this.getColumnSearchProps('Tags'),
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.
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].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text) => (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[this.state.searchText]}
autoEscape
textToHighlight={text.toString()}
/>
),
})
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 with that line, the application stops rendering and I got multiple errors like this:
Error on interface:
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 | }
what can I do to fix it?
You need to send the current version to the front-end somehow. Is it metadata that can be retrieved from your azure pipeline? Do you perhaps have pre or postbuild scripts? These could update a variable (++) in a database that you could either retrieve from the front-end with an ajax call or send along when downloading the Js bundle from your server.
When you have retrieved the data somewhere somehow in your back-end, you may also consider passing the version number as a header in the http response.

Resources