Related
I am working on the table content which has 5 rows . few rows content description is same so I need to show only one row in this case and give expan button. when expand button is clicked it should show all the rows which has the same associated description. I am pasting the screenshot which I got as output .
In the above screenshot I've got the "-" button for all the rows which has same description. but I need only one "-"(collapse) button for "paytm" and one "-"button for "Paypal". and when they are clicked only one paytm, PayPal should be displayed.
let rows = [
{
id: { value: '' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2020' },
},
{
id: { value: '' },
description: { value: 'paypal' },
DueDate: { value: '04/04/2021' }
},
{
id: { value: '' },
description: { value: 'paypal' },
DueDate: { value: '04/03/2020' }
},
{
id: { value: '' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2021' }
},
{
id: { value: '' },
description: { value: 'Gpay' },
DueDate: { value: '04/03/2020' }
},
];
I am showing the table based on the lasted date and check if there exists any multiple same descriptions and putting them all in one object.
const descriptionSortedArray = rows.reduce((acc, current) => {
acc[current.description.value] = [
...(acc[current.description.value] || []),
current,
];
return acc;
}, {});
console.log(descriptionSortedArray);
and transforming the object based on latest date
const transformedRows = Object.keys(descriptionSortedArray).reduce(
(acc, current) => {
acc[current] = sortRowsByDate(descriptionSortedArray[current]);
return acc;
},
{}
);
// console.log(Object.keys(descriptionSortedArray));
console.log({ transformedRows });
and getting the key values for them by using object.keys and mapping over them.
x.[paytm:[], Gpay:[], PayPal :[]];
based on the inner array key length I am showing button (expand and collapse)if
x[paytm]>1 ?show button: without button
code is below
import React, { Component } from 'react';
import './style.css';
export default class App extends React.Component {
render() {
let rows = [
{
id: { value: '' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2020' },
},
{
id: { value: '' },
description: { value: 'paypal' },
DueDate: { value: '04/04/2021' }
},
{
id: { value: '' },
description: { value: 'paypal' },
DueDate: { value: '04/03/2020' }
},
{
id: { value: '' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2021' }
},
{
id: { value: '' },
description: { value: 'Gpay' },
DueDate: { value: '04/03/2020' }
},
];
const descriptionSortedArray = rows.reduce((acc, current) => {
acc[current.description.value] = [
...(acc[current.description.value] || []),
current,
];
return acc;
}, {});
console.log(descriptionSortedArray);
const sortRowsByDate = (rows) =>
rows.sort(
(a, b) => new Date(b.DueDate.value) - new Date(a.DueDate.value)
);
const transformedRows = Object.keys(descriptionSortedArray).reduce(
(acc, current) => {
acc[current] = sortRowsByDate(descriptionSortedArray[current]);
return acc;
},
{}
);
// console.log(Object.keys(descriptionSortedArray));
console.log({ transformedRows });
return (
<div>
<table>
<tr>
<th>id</th>
<th>description</th>
<th>duedate</th>
<th></th>
</tr>
{Object.keys(transformedRows).map((rowKey) => {
// console.log("rowKey===", rowKey)
// console.log(transformedRows[rowKey])
return (
<tbody>
{transformedRows[rowKey].length > 1
? transformedRows[rowKey].map((obj) => (
<tr>
<td>{obj.id.value}</td>
<td>{obj.description.value}</td>
<td>{obj.DueDate.value}</td>
<td>{<button>-</button>}</td>
</tr>
))
: transformedRows[rowKey].map((obj) => (
<tr>
<td>{obj.id.value}</td>
<td>{obj.description.value}</td>
<td>{obj.DueDate.value}</td>
<td></td>
</tr>
))}
</tbody>
);
})}
</table>
</div>
);
}
}
Please help in this. I need to show only one collapse button for the rows having same description(paytm is repeated show them only in one row give "expand" and "collapse" button). when even button is clicked it should be toggled. Please help
You can keep another field called visible along with your data array and toggle its value when clicked on the button.
Define a state to store the transformedRows
state = {
transformedRows: {}
};
Do the transformation like below in componentDidMount.
componentDidMount = () => {
const descriptionSortedArray = rows.reduce((acc, current) => {
acc[current.description.value] = {
...acc[current.description.value],
data: [...(acc[current.description.value]?.["data"] ?? []), current],
visible: false
};
return acc;
}, {});
const sortRowsByDate = (rows) =>
rows.sort(
(a, b) => new Date(b.DueDate.value) - new Date(a.DueDate.value.data)
);
const transformedRows = Object.keys(descriptionSortedArray).reduce(
(acc, current) => {
acc[current] = {
...descriptionSortedArray[current],
data: sortRowsByDate(descriptionSortedArray[current]["data"])
};
return acc;
},
{}
);
this.setState({ transformedRows });
};
Toggle the visible state when clicking on the button.
handleToggle = (entry, visibility) => {
this.setState((prevState) => {
return {
...prevState,
transformedRows: Object.fromEntries(
Object.entries(prevState.transformedRows).map(([key, value]) => {
if (key === entry) {
return [key, { ...value, visible: visibility }];
} else {
return [key, value];
}
})
)
};
});
};
Render rows as below.
<tbody>
{Object.entries(transformedRows).map(([key, { data, visible }]) => {
if (data.length > 1) {
return data.map((item, index) => (
<tr>
{(index === 0 || (index >= 1 && visible)) && (
<>
<td>{item.id.value}</td>
<td>{item.description.value}</td>
<td>{item.DueDate.value}</td>
</>
)}
{index === 0 && (
<td>
{
<button
onClick={() => {
this.handleToggle(key, !visible);
}}
>
toggle
</button>
}
</td>
)}
</tr>
));
} else {
return data.map(item => (
<tr>
<td>{item.id.value}</td>
<td>{item.description.value}</td>
<td>{item.DueDate.value}</td>
</tr>
));
}
})}
</tbody>
Create an accordion component as follow:
React accordion
Then use it as follow:
return (
<div>
<table>
<thead>
<tr>
<th>id</th>
<th>description</th>
<th>duedate</th>
<th></th>
</tr>
</thead>
{Object.keys(transformedRows).map((rowKey) => {
// console.log("rowKey===", rowKey)
// console.log(transformedRows[rowKey])
console.log(rowKey);
return (
<tbody key={rowKey}>
{transformedRows[rowKey].length > 1 ? (
<tr>
<td colSpan="4">
<Accordion label={rowKey}>
{transformedRows[rowKey].map((obj) => (
<div key={obj.id.value}>
<span>{obj.id.value}</span>
<span>{obj.description.value}</span>
<span>{obj.DueDate.value}</span>
<span>{<button>-</button>}</span>
</div>
))}
</Accordion>
</td>
</tr>
) : (
transformedRows[rowKey].map((obj) => (
<tr key={obj.id.value}>
<td>{obj.id.value}</td>
<td>{obj.description.value}</td>
<td>{obj.DueDate.value}</td>
<td></td>
</tr>
))
)}
</tbody>
);
})}
</table>
</div>
);
Full code:
let rows = [
{
id: { value: '1' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2020' },
},
{
id: { value: '2' },
description: { value: 'paypal' },
DueDate: { value: '04/04/2021' },
},
{
id: { value: '3' },
description: { value: 'paypal' },
DueDate: { value: '04/03/2020' },
},
{
id: { value: '4' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2021' },
},
{
id: { value: '5' },
description: { value: 'Gpay' },
DueDate: { value: '04/03/2020' },
},
];
const descriptionSortedArray = rows.reduce((acc, current) => {
acc[current.description.value] = [...(acc[current.description.value] || []), current];
return acc;
}, {});
console.log(descriptionSortedArray);
const sortRowsByDate = (rows) =>
rows.sort((a, b) => new Date(b.DueDate.value) - new Date(a.DueDate.value));
const transformedRows = Object.keys(descriptionSortedArray).reduce((acc, current) => {
acc[current] = sortRowsByDate(descriptionSortedArray[current]);
return acc;
}, {});
return (
<div>
<table>
<thead>
<tr>
<th>id</th>
<th>description</th>
<th>duedate</th>
<th></th>
</tr>
</thead>
{Object.keys(transformedRows).map((rowKey) => {
// console.log("rowKey===", rowKey)
// console.log(transformedRows[rowKey])
console.log(rowKey);
return (
<tbody key={rowKey}>
{transformedRows[rowKey].length > 1 ? (
<tr>
<td colSpan="4">
<Accordion label={rowKey}>
{transformedRows[rowKey].map((obj) => (
<div key={obj.id.value}>
<span>{obj.id.value}</span>
<span>{obj.description.value}</span>
<span>{obj.DueDate.value}</span>
<span>{<button>-</button>}</span>
</div>
))}
</Accordion>
</td>
</tr>
) : (
transformedRows[rowKey].map((obj) => (
<tr key={obj.id.value}>
<td>{obj.id.value}</td>
<td>{obj.description.value}</td>
<td>{obj.DueDate.value}</td>
<td></td>
</tr>
))
)}
</tbody>
);
})}
</table>
</div>
);
This is 2 tables that I've created.
and it's its data
const callData = [
{ id: 1, type: "call", volume: 50000, strike: 3000 },
{ id: 2, type: "call", volume: 30000, strike: 5000 },
{ id: 3, type: "call", volume: 20000, strike: 7000 }
];
const putData = [
{ id: 1, type: "put", volume: 50000, strike: 3000 },
{ id: 2, type: "put", volume: 10000, strike: 5000 },
{ id: 3, type: "put", volume: 7000, strike: 7000 }
];
If you click the row, it will print something like
const rowOnClick = (row) => {
let obj = row.values;
console.log(`clicked ${obj.type} for Strike ${obj.strike}`);
};
// log "clicked call for Strike 3000"
Now I want to merge these 2 tables together.
The one single table will be like
If I click the row in red box, it should print clicked call for Strike xxx.
If I click the row in green box, it should print clicked put for Strike xxx.
For example, if I click the row in red box for strike 5000, it prints clicked call for Strike 5000
How can I do it?
Codesandbox
https://codesandbox.io/s/vigilant-easley-v6h8p?file=/src/App.js:0-2789
App.js
import React, { useEffect } from "react";
import styled from "styled-components";
import { useTable } from "react-table";
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid black;
tr {
:last-child {
td {
border-bottom: 0;
}
}
}
th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
:last-child {
border-right: 0;
}
}
}
`;
function Table({ columns, data }) {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = useTable({
columns,
data
});
const rowOnClick = (row) => {
let obj = row.values;
console.log(`clicked ${obj.type} for Strike ${obj.strike}`);
};
// Render the UI for your table
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()} onClick={() => {}}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row);
return (
<tr
{...row.getRowProps()}
onClick={() => {
rowOnClick(row);
}}
>
{row.cells.map((cell) => {
return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
})}
</tr>
);
})}
</tbody>
</table>
);
}
function App() {
const columns = React.useMemo(
() => [
{
Header: "Info",
columns: [
{
Header: "Type",
accessor: "type"
},
{
Header: "Volume",
accessor: "volume"
},
{
Header: "Strike",
accessor: "strike"
}
]
}
],
[]
);
// got below data from async request
const callData = [
{ id: 1, type: "call", volume: 50000, strike: 3000 },
{ id: 2, type: "call", volume: 30000, strike: 5000 },
{ id: 3, type: "call", volume: 20000, strike: 7000 }
];
const putData = [
{ id: 1, type: "put", volume: 50000, strike: 3000 },
{ id: 2, type: "put", volume: 10000, strike: 5000 },
{ id: 3, type: "put", volume: 7000, strike: 7000 }
];
return (
<Styles>
<Table columns={columns} data={callData} />
<Table columns={columns} data={putData} />
</Styles>
);
}
export default App;
You can merge your data into one object and render juse one table. Also instaed of using onClick on the tr element, you can handle your requirement by using onClick on the td elements and decide what data should be send to rowOnClick method, on the callback of td's onClick.like this:
{rows.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
...
return <td {...cell.getCellProps()}
onClick={ column === 'strike' ? undefined : ()=> rowOnClick({...})}>
{cell.render("Cell")}</td>;
})}
</tr>
);
})}
I implemented an example here on codesandbox you can check it out.
Goal:
Display react bootstrap's modal when you press the button 'Open Modal'
Problem:
I do not know how to make it to show bootstrap's modal when I press the button 'Open Modal'
What part am I missing?
Stackblitz:
https://stackblitz.com/edit/react-bootstrap-examples-suktpo?
Info:
*I'm newbie in Reactjs
Thank you!
index.html
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<div id="root"></div>
index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
import DisplayModalContent from './DisplayModalContent';
import { Modal, Button } from 'react-bootstrap';
class App extends Component {
constructor() {
super();
this.state = {
openItem: null,
items: [
{
firstName: 'Josef',
lastName: 'Anderson',
key: 'josef.anderson',
startYear: 2021,
startMonth: 2
},
{
firstName: 'Jim',
lastName: 'West',
key: 'jim.west',
startYear: 2020,
startMonth: 3
},
{
firstName: 'Joe',
lastName: 'West',
key: 'joe.west',
startYear: 1998,
startMonth: 10
}
],
firstName: '',
lastName: ''
};
}
handleOpenModal = openItem => {
this.setState({ openItem });
};
handleCloseModal = () => {
this.setState({ openItem: null });
};
handleOpenItemValue = e => {
let { name, value } = e.target;
this.setState({
openItem: { ...this.state.openItem, [name]: value }
});
};
handleSubmit = () => {
console.log(document.getElementsByName('startMonth')[0].value);
alert(
JSON.stringify({
test: document.getElementsByName('startMonth')[0].value
})
);
};
render() {
const { items, openItem } = this.state;
return (
<div>
<table border="1">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th />
</tr>
</thead>
<tbody>
{items.map(item => {
const { firstName, lastName, key } = item;
return (
<tr key={key}>
<td>{firstName}</td>
<td>{lastName}</td>
<td>
<button onClick={() => this.handleOpenModal(item)}>
Open Modal
</button>
</td>
</tr>
);
})}
</tbody>
</table>
<DisplayModalContent item={openItem} />
</div>
);
}
}
render(<App />, document.getElementById('root'));
DisplayModalContent.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
import { Modal, Button } from 'react-bootstrap';
const options = [
{ value: 1, label: 'Jan' },
{ value: 2, label: 'Feb' },
{ value: 3, label: 'Mars' },
{ value: 4, label: 'April' },
{ value: 5, label: 'May' },
{ value: 6, label: 'June' },
{ value: 7, label: 'July' },
{ value: 8, label: 'August' },
{ value: 9, label: 'Sept' },
{ value: 10, label: 'Oct' },
{ value: 11, label: 'Nov' },
{ value: 12, label: 'Dec' }
];
class DisplayModalContent extends Component {
constructor() {
super();
this.state = {
openItem: null,
firstName: '',
lastName: ''
};
}
componentDidUpdate(s) {
if (JSON.stringify(this.props) !== JSON.stringify(s)) {
this.setState({ openItem: this.props.item });
}
}
handleOpenModal = openItem => {
this.setState({ openItem });
};
handleCloseModal = () => {
this.setState({ openItem: null });
};
handleOpenItemValue = e => {
let { name, value } = e.target;
this.setState({
openItem: { ...this.state.openItem, [name]: value }
});
};
handleSubmit = () => {
console.log(document.getElementsByName('startMonth')[0].value);
alert(
JSON.stringify({
test: document.getElementsByName('startMonth')[0].value
})
);
};
hideShowModal = () => {
this.setState({ isModalOpen: !this.state.isModalOpen });
};
render() {
const { items, openItem } = this.state;
return (
<div>
{openItem !== null && (
<div isOpen={true}>
<Button variant="primary" onClick={() => this.hideShowModal()}>
Click to hide/show
</Button>
<Modal
show={this.state.isModalOpen}
onHide={() => this.hideShowModal()}
>
<Modal.Header closeButton>
<Modal.Title>This is modal title</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
First Name:
<br />
<input
type="text"
id="firstName"
name="firstName"
value={openItem.firstName}
onChange={e => this.handleOpenItemValue(e)}
/>
<input
type="text"
id="lastName"
name="lastName"
value={openItem.lastName}
onChange={e => this.handleOpenItemValue(e)}
/>
</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary">CLOSE</Button>
<Button variant="primary">SAVE</Button>
</Modal.Footer>
</Modal>
</div>
)}
</div>
);
}
}
export default DisplayModalContent;
you don't have to use the state in the two components, just define the state in the parent component and pass it as a prop to the child component.
index.js
import React, { Component } from "react";
import DisplayModalContent from "./DisplayModalContent";
import { Modal, Button } from "react-bootstrap";
const items = [
{
firstName: "Josef",
lastName: "Anderson",
key: "josef.anderson",
startYear: 2021,
startMonth: 2
},
{
firstName: "Jim",
lastName: "West",
key: "jim.west",
startYear: 2020,
startMonth: 3
},
{
firstName: "Joe",
lastName: "West",
key: "joe.west",
startYear: 1998,
startMonth: 10
}
];
class App extends Component {
constructor() {
super();
this.state = {
open: false,
openItem: null,
items: [],
firstName: "",
lastName: ""
};
}
componentDidMount() {
this.setState({ items });
}
handleOpenModal = (openItem) => {
this.setState({ openItem, open: true });
};
handleCloseModal = () => {
this.setState({ open: false });
};
handleOpenItemValue = (e) => {
let { name, value } = e.target;
this.setState({
openItem: { ...this.state.openItem, [name]: value }
});
};
handleSubmit = (key) => {
const { openItem, items } = this.state;
const updatedItems = items.filter((i) => i.key !== key);
this.setState({
open: false,
items: [...updatedItems, openItem]
});
// console.log(document.getElementsByName("startMonth")[0].value);
// alert(
// JSON.stringify({
// test: document.getElementsByName("startMonth")[0].value
// })
// );
};
render() {
const { items, openItem, open } = this.state;
return (
<div>
<table border="1">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th />
</tr>
</thead>
<tbody>
{items &&
items.map((item) => {
const { firstName, lastName, key } = item;
return (
<tr key={key}>
<td>{firstName}</td>
<td>{lastName}</td>
<td>
<button onClick={() => this.handleOpenModal(item)}>
Open Modal
</button>
</td>
</tr>
);
})}
</tbody>
</table>
<DisplayModalContent
item={openItem}
isOpen={open}
onClose={this.handleCloseModal}
onSubmit={this.handleSubmit}
handleOpenItemValue={(e) => this.handleOpenItemValue(e)}
/>
<br />
<div> {JSON.stringify(items)} </div>
</div>
);
}
}
export default App;
DisplayModalContent.js
import React, { Component } from "react";
import { Modal, Button } from "react-bootstrap";
const options = [
{ value: 1, label: "Jan" },
{ value: 2, label: "Feb" },
{ value: 3, label: "Mars" },
{ value: 4, label: "April" },
{ value: 5, label: "May" },
{ value: 6, label: "June" },
{ value: 7, label: "July" },
{ value: 8, label: "August" },
{ value: 9, label: "Sept" },
{ value: 10, label: "Oct" },
{ value: 11, label: "Nov" },
{ value: 12, label: "Dec" }
];
class DisplayModalContent extends Component {
render() {
const { item, isOpen, onClose, handleOpenItemValue, onSubmit } = this.props;
return (
<div>
<div>
<Modal show={isOpen} onHide={onClose}>
<Modal.Header closeButton>
<Modal.Title>This is modal title</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
First Name:
<br />
<input
type="text"
id="firstName"
name="firstName"
value={item && item.firstName}
onChange={(e) => handleOpenItemValue(e)}
/>
<input
type="text"
id="lastName"
name="lastName"
value={item && item.lastName}
onChange={(e) => handleOpenItemValue(e)}
/>
</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={onClose}>
CLOSE
</Button>
<Button variant="primary" onClick={() => onSubmit(item.key)}>
SAVE
</Button>
</Modal.Footer>
</Modal>
</div>
</div>
);
}
}
export default DisplayModalContent;
Live working Demo
I was reviewing your code and I see you're using this.state.isModalOpen to show the modal, but in the DisplayModalContent component that state is never updated when you're receiving the props from your index. So, the easy fix for that is update isModalOpen when you are receiving the props. However, from my experience I would recommend do not use a new state to handle the modal open state in the DisplayModalContent component, you can use it directly from the props:
<DisplayModalContent
item={openItem}
isOpen={!!openItem}
onClose={this.handleCloseModal}
/>
and in your DisplayModalContent component, you can replace any local state and in order to use the component props:
...
<Modal
show={this.props.isOpen}
onHide={this.props.onClose}
>
...
By doing this your code will be more simple and readable.
I try to make function that get an array of object and according to object in the array generate a table with dynamic rowspan.
I tried many solutions but none of them helped.
I tried this code,but I did not continue it because the beginning did not work well
const returnTabel = state => {
return state.map((item, index) => {
return (
<tr key={index}>
{Object.keys(item).map((key, index) => {
if (Array.isArray(item[key])) {
return item[key].map((object, index) => {
return Object.keys(object).map((i, index) => {
if (Array.isArray(object[i])) {
} else {
return (
<tr>
<td>{object[i]}</td>
</tr>
);
}
});
});
} else {
return (
<td rowSpan={2} key={index}>
{item[key]}
</td>
);
}
})}
</tr>
);
});};
Here is my data:
const state = [
{
name: 'Bill',
info: [
{
hobby: 'Practice',
field: [
{ type: 'Swim', hours: '6' },
{ type: 'Run', hours: '7' }
]
},
{
hobby: 'Listen to music',
field: [
{ type: 'Jazz', hours: '3' },
{ type: 'Electronic music', hours: '3' },
{ type: 'Hip hop', hours: '3' }
]
}
],
student: 'No'
},
{
name: 'John',
info: [
{
hobby: 'Practice',
field: [
{ type: 'Swim', hours: '1' },
{ type: 'Run', hours: '2' }
]
}
],
student: 'Yes'
}]
I want to make this table with my data
You can simplify the render mapping if you map the data to rows that look like:
[{"txt":"Bill","rowSpan":5},{"txt":"Practice","rowSpan":2},{"txt":"Swim"},{"txt":"6"},{"txt":"No","rowSpan":5}]
//OR
[null,{"txt":"Listen to music","rowSpan":3},{"txt":"Jazz"},{"txt":"3"},null]
//OR
[null,null,{"txt":"Run"},{"txt":"7"},null]
Then the render simplifies down to:
return (
<table border="1">
{rows.map(cells => (
<tr>
{cells.map(cell => cell && <td rowSpan={cell.rowSpan}>{cell.txt}</td>)}
</tr>
)
)}
</table>
);
Working example
const data=[{name:"Bill",info:[{hobby:"Practice",field:[{type:"Swim",hours:"6"},{type:"Run",hours:"7"}]},{hobby:"Listen to music",field:[{type:"Jazz",hours:"3"},{type:"Electronic music",hours:"3"},{type:"Hip hop",hours:"3"}]}],student:"No"},{name:"John",info:[{hobby:"Practice",field:[{type:"Swim",hours:"1"},{type:"Run",hours:"2"}]}],student:"Yes"}];
const rowData = data.reduce((a, { name, info, student }) => {
const rowSpan = info.reduce((a, { field }) => a + field.length, 0);
let [firstCell, lastCell] = [name, student].map(txt => ({ txt, rowSpan }));
info.forEach(({ hobby, field }, i) => {
const rowSpan = field.length;
let hobCell = { txt: hobby, rowSpan };
field.forEach((f, j) => {
const fieldCells = Object.values(f).map(txt => ({ txt }));
if (j > 0) {
hobCell = firstCell = lastCell = null;
}
const row = [firstCell, hobCell, ...fieldCells, lastCell];
a.push(row);
});
});
return a;
}, []);
console.log( rowData)
const Table = () => {
const [rows] = React.useState(rowData);
return (
<table border="1">
{rows.map((cells,i) => (
<tr key={i}>
{cells.map((cell,j) => cell && <td key={`${i}-${j}`} rowSpan={cell.rowSpan}>{cell.txt}</td>)}
</tr>
)
)}
</table>
);
};
// Render it
ReactDOM.render(
<Table />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I am working with antd table and antd transfer component and I am facing a small challenge with CSS.
I have created a small example with codesandbox. If I try to expand a row, you will see that other columns try to adjust themselves. Is there a way I could prevent this? I do not want the rows to adjust themselves. The table should feel the same after the expansion as it was before the expansion.
This is code from the sandbox link I shared above that generates the table.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Transfer, Table, Tag } from "antd";
function difference(listOne, listTwo) {
const set1 = new Set(listOne);
const set2 = new Set(listTwo);
const difference = new Set([...set1].filter(x => !set2.has(x)));
return Array.from(difference);
}
// Customize Table Transfer
const TableTransfer = ({ leftColumns, rightColumns, ...restProps }) => (
<Transfer {...restProps}>
{({
direction,
filteredItems,
onItemSelectAll,
onItemSelect,
selectedKeys: listSelectedKeys,
disabled: listDisabled
}) => {
const columns = direction === "left" ? leftColumns : rightColumns;
const rowSelection = {
getCheckboxProps: item => ({ disabled: listDisabled || item.disabled }),
onSelectAll(selected, selectedRows) {
const treeSelectedKeys = selectedRows
.filter(item => !item.disabled)
.map(({ key }) => key);
const diffKeys = selected
? difference(treeSelectedKeys, listSelectedKeys)
: difference(listSelectedKeys, treeSelectedKeys);
onItemSelectAll(diffKeys, selected);
},
onSelect({ key }, selected) {
onItemSelect(key, selected);
},
selectedRowKeys: listSelectedKeys
};
return (
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={filteredItems}
size="small"
/>
);
}}
</Transfer>
);
const mockTags = ["eg", "gg", "e"];
const mockData = [];
for (let i = 0; i < 20; i++) {
let data = {
key: i.toString(),
title: `eq${i + 1}`,
description: `description of eq${i + 1}`,
disabled: false, //i % 4 === 0,
tag: mockTags[i % 3]
};
if (i % 2 === 0) {
const children = [
{
key: i.toString() + "children",
title: `children-${i + 1}`,
description: `children description-${i + 1}`,
disabled: true,
tag: "tag"
}
];
data["children"] = children;
}
mockData.push(data);
}
const originTargetKeys = mockData
.filter(item => +item.key % 3 > 1)
.map(item => item.key);
const leftTableColumns = [
{
dataIndex: "title",
title: "Name"
},
{
dataIndex: "tag",
title: "Tag",
render: tag => <Tag>{tag}</Tag>
},
{
dataIndex: "description",
title: "Description"
}
];
const rightTableColumns = [
{
dataIndex: "title",
title: "Names"
},
{
dataIndex: "tag",
title: "Tag",
render: tag => <Tag>{tag}</Tag>
},
{
dataIndex: "description",
title: "Description"
}
];
class App extends React.Component {
state = {
targetKeys: originTargetKeys
};
onChange = nextTargetKeys => {
this.setState({ targetKeys: nextTargetKeys });
};
render() {
const { targetKeys, disabled } = this.state;
return (
<div>
<TableTransfer
className="table-transfer"
dataSource={mockData}
titles={[
<div>
<input type="checkbox" checked />
Equipment <input type="checkbox" checked /> Groups
</div>,
<div>
<input type="checkbox" checked />
Equipment <input type="checkbox" checked /> Groups
</div>
]}
targetKeys={targetKeys}
disabled={disabled}
showSearch={true}
onChange={this.onChange}
filterOption={(inputValue, item) =>
item.title.indexOf(inputValue) !== -1 ||
item.tag.indexOf(inputValue) !== -1
}
leftColumns={leftTableColumns}
rightColumns={rightTableColumns}
locale={{
itemUnit: "Equipment",
itemsUnit: "Equipments",
notFoundContent: "The list is empty",
searchPlaceholder: "Search here"
}}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
You want to constraint your columns width, for example, try this on the leftTableColumns and notice the difference with the right one:
const leftTableColumns = [
{
dataIndex: 'title',
title: 'Name',
width: '45%'
},
{
dataIndex: 'tag',
title: 'Tag',
render: tag => <Tag>{tag}</Tag>,
width: '10%'
},
{
dataIndex: 'description',
title: 'Description',
width: '40%'
}
];
Refer to Table Column API.
Fork of your codebox: