I am trying to create a ASC & DESC effect on my table headers in my React app.
Example here.
When I consoled the arguments I discovered the function it self was firing twice:
Once with the correct values and again with another value :(
Here is a bit of the code:
function useActiveToggle(initialState = activeObj, bool = false) {
const [state, setState] = React.useState(initialState);
const toggle = React.useCallback((id) => {
return setState((previousState) => ({ ...previousState, [id]: !previousState[id] }))
}, [])
return [state, toggle]
}
function TableHeaders({ headerData, handleDataSort }) {
function convertCamelToTitleCase(text) {
const result = text.replace(/([A-Z])/g, " $1");
return result.charAt(0).toUpperCase() + result.slice(1);
}
const options = [
{ label: 'First name', value: 'First name' },
{ label: 'Last name', value: 'Last name' },
];
const [value, setValue] = React.useState('First name');
const [active, toggle] = useActiveToggle()
function handleChange(event) {
console.log('event.target.value', event.target.value)
event.target.value === 'First name' ? handleDataSort("firstName", 1) : handleDataSort("lastName", 1)
setValue(event.target.value);
};
function handleClick(e, id, headerName) {
e.preventDefault();
if (active[id] === false && value === 'First name') {
handleDataSort(headerName, -1)
} else {
handleDataSort(headerName, 1)
}
if (active[id] === false && value === 'Last name') {
handleDataSort(headerName, -1)
} else {
handleDataSort(headerName, 1)
}
toggle(id)
}
return (
<>
<thead>
<th>
<Dropdown
label="Sort by:"
options={options}
value={value}
setValue={setValue}
handleChange={handleChange}
handleDataSort={handleDataSort}
/>
</th>
{headerData.map((headerName, index) => {
return (
<th key={index + headerName} className={`${headerName === 'firstName' || headerName === 'lastName' ? "table-header" : null}`} scope="col" onClick={(e) => handleClick(e, index, headerName)} style={!active[index] ? { backgroundColor: 'white' } : { backgroundColor: 'grey' }} >
{convertCamelToTitleCase(headerName)}
</th>
);
})}
</thead>
</>
);
}
function TableData({ rowData }) {
return (
<tbody>
{rowData.map(
({
company,
dateJoined,
firstName,
id,
isSiteAdmin,
jobTitle,
lastName,
state
}) => {
return (
<tr key={id}>
<td></td>
<td>{id}</td>
<td>{firstName}</td>
<td>{lastName}</td>
<td>{company}</td>
<td>{jobTitle}</td>
<td>{state}</td>
<td>{isSiteAdmin}</td>
<td>{dateJoined}</td>
</tr>
);
}
)}
</tbody>
);
}
const MemoTableData = React.memo(TableData);
function App() {
const [userData, setUserData] = React.useState("data")
const [headerData, setHeaderData] = React.useState(() =>
Object.keys(userData[0])
);
const [rowData, setRowData] = React.useState([]);
const [sortType, setSortType] = React.useState("firstName");
const sortArray = (p, o) => {
console.log("p, o ", p, o);
const sorted = [...userData].sort((a, b) => a[p].localeCompare(b[p]) * o)
setRowData(sorted);
};
React.useEffect(() => {
sortArray(sortType, 1);
}, [sortType]);
return (
<table>
<TableHeaders headerData={headerData} handleDataSort={sortArray} />
<MemoTableData rowData={rowData} />
</table>
);
}
export default App;
Related
Been trying to figure this out for a couple hours now but I'm stumped. According to the console, when I make a patch request, the request goes through and actually updates the information, but my map function is breaking after that and it renders a blank page.
Here's the component with the error:
import { useState, useEffect } from "react"
// import { EmployeeForm } from "./EmployeeForm"
export function EmployeeTable() {
const [employees, setEmployees] = useState([])
const [employeeId, setEmployeeId] = useState([])
const [update, setUpdate] = useState(false)
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
useEffect(() => {
fetch('/api/employees')
.then(res => res.json())
.then(json => setEmployees(json.employees)
)
}, [])
const updateEmployee = async () => {
try {
const res = await fetch(`/api/employees/${employeeId}`,
{method: 'PATCH', body: JSON.stringify({firstName, lastName})})
const json = await res.json()
const employeesCopy = [...employees]
const index = employees.findIndex((employee) => employee.id === employeeId)
employeesCopy[index] = json.employee
setEmployees(employeesCopy)
setFirstName('')
setLastName('')
setUpdate(false)
setEmployeeId([])
} catch (err) {
console.log(err)
}
}
const submitForm = async (event) => {
event.preventDefault()
if(update){
updateEmployee()
}
}
const deleteEmployee = async (id) => {
try {
await fetch(`/api/employees/${id}`, {method: 'DELETE'})
setEmployees(employees.filter(employee => employee.id !== id))
} catch (error) {
}
}
const setEmployeeToUpdate = (id) => {
const employee = employees.find(emp => emp.id === id)
if(!employee) return
setUpdate(true)
setEmployeeId(employee.id)
setFirstName(employee.firstName)
setLastName(employee.lastName)
}
return (
<div>
<header>
<h1>Employees</h1>
</header>
{employees.length > 0 ? (
<table>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{employees.map(({id, firstName, lastName}) => {
return(
<tr key={id}>
<td>{firstName}</td>
<td>{lastName}</td>
<td>
<button onClick={() => setEmployeeToUpdate(id)}>UPDATE</button>
<button onClick={() => deleteEmployee(id)}>DELETE</button>
</td>
</tr>
)
})}
</tbody>
</table>
) : (
// If for some reason the employees cannot be returned the page will render this p tag.
<p>No employees</p>
)}
<form onSubmit={submitForm}>
<div>
<div>
<input type="text" value={firstName} onChange={e => setFirstName(e.target.value)}/>
</div>
<div>
<input type="text" value={lastName} onChange={e => setLastName(e.target.value)}/>
</div>
<div>
<button type='submit'>{update ? 'Update' : 'Create'}</button>
</div>
</div>
</form>
</div>
)
}
And here is the MirageJS server.js
import { createServer, Model } from "miragejs";
import faker from "faker";
import avatar from "./avatar.png";
export function makeServer({ environment = "test" } = {}) {
let server = createServer({
environment,
models: {
employee: Model,
},
seeds(server) {
for (let i = 0; i < 10; i++) {
server.create("employee", {
id: faker.datatype.uuid(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
email: faker.internet.email(),
phone: faker.phone.phoneNumber(),
bio: faker.lorem.paragraph(),
avatar: avatar,
address: {
streetAddress: `${faker.address.streetAddress()} ${faker.address.streetName()}`,
city: faker.address.city(),
state: faker.address.stateAbbr(),
zipCode: faker.address.zipCode(),
},
});
}
},
routes() {
this.namespace = "api";
this.get(
"/employees",
(schema) => {
return schema.employees.all();
},
{ timing: 1000 }
);
this.patch(
"/employees/:id",
(schema, request) => {
const attrs = JSON.parse(request.requestBody);
const employee = schema.employees.find(request.params.id);
employee.update(attrs);
},
{ timing: 300 }
);
this.delete(
"/employees/:id",
(schema, request) => {
const employee = schema.employees.find(request.params.id);
employee.destroy();
return new Response();
},
{ timing: 300 }
);
},
});
return server;
}
Provide a default value, to destructure from employees.map(({id, firstName, lastName}).
{employees.filter(item => item).map(({ id = 0, firstName = 'empty', lastName = 'empty' }) => {...
setEmployees((prevState) => {
const index = prevState.findIndex((employee) => employee.id === employeeId);
let newEmployees = […prevState];
newEmployees[index] = json.employee;
return newEmployees;
}))
You miss return in server.js patch request.
server.js
this.patch(
"/employees/:id",
(schema, request) => {
const attrs = JSON.parse(request.requestBody);
const employee = schema.employees.find(request.params.id);
return employee.update(attrs);
},
{ timing: 300 }
);
I am trying to sort a table (ascending/descending) when my table header data is placed inside a .map() method (see line 73 of the codesandbox link in this post).
I can get the hand emoji to change onClick, but no sorting takes place. I think it may have something to do with passing an object to the map method, and not an individual string or numeric value as found in the codesandbox that I have based this functionality on. Here is the original sandbox that I am modelling after:
https://codesandbox.io/embed/table-sorting-example-ur2z9?fontsize=14&hidenavigation=1&theme=dark
...and here is my codesandbox with my data structure that I need to sort:
https://codesandbox.io/s/table-sorting-example-forked-dofhj?file=/src/App.js
What can I change in order to get each column to be sortable? Any help/advice would be greatly appreciated.
App.js
import React from "react";
import "./styles.css";
import {
Button,
Table,
Thead,
Tbody,
Flex,
Tooltip,
Tr,
Th,
Td
} from "#chakra-ui/react";
//Table Headers
const TABLE_HEADERS = [
{ name: "ID Number" },
{ name: "User Type" },
{ name: "User Category" },
{ name: "User Interest" }
];
const useSortableData = (items, config = null) => {
const [sortConfig, setSortConfig] = React.useState(config);
const sortedItems = React.useMemo(() => {
let sortableItems = [...items];
if (sortConfig !== null) {
sortableItems.sort((a, b) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === "ascending" ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === "ascending" ? 1 : -1;
}
return 0;
});
}
return sortableItems;
}, [items, sortConfig]);
const requestSort = (key) => {
let direction = "ascending";
if (
sortConfig &&
sortConfig.key === key &&
sortConfig.direction === "ascending"
) {
direction = "descending";
}
setSortConfig({ key, direction });
};
return { items: sortedItems, requestSort, sortConfig };
};
const ProductTable = (props) => {
const { items, requestSort, sortConfig } = useSortableData(
props.myUserErrorTypes
);
const getClassNamesFor = (name) => {
if (!sortConfig) {
return;
}
return sortConfig.key === name ? sortConfig.direction : undefined;
};
return (
<Table>
<caption>User Error Types</caption>
<Thead>
<Tr>
{TABLE_HEADERS.map(({ name, description, isNumeric }) => (
<Th key={name} isNumeric={isNumeric}>
<Button
type="button"
onClick={() => requestSort(name)}
className={getClassNamesFor(name)}
>
<Tooltip label={description} aria-label={description}>
{name}
</Tooltip>
</Button>
</Th>
))}
</Tr>
</Thead>
<Tbody>
{items.map((error) => {
const { userNumber, userType, errorId, errorCategory } = error;
return (
<React.Fragment key={errorId}>
<Tr id={errorId} key={errorId}>
<Td>{userNumber}</Td>
<Td>{userType}</Td>
<Td>{errorId}</Td>
<Td>{errorCategory}</Td>
<Td textAlign="center">
<Flex justify="justifyContent"></Flex>
</Td>
</Tr>
</React.Fragment>
);
})}
</Tbody>
</Table>
);
};
export default function App() {
return (
<div className="App">
<ProductTable
myUserErrorTypes={[
{
userNumber: 1234567890,
userType: "SuperUser",
errorId: 406,
errorCategory: "In-Progress"
},
{
userNumber: 4859687937,
userType: "NewUser",
errorId: 333,
errorCategory: "Complete"
}
]}
/>
</div>
);
}
The items are being sorted by the table header name (e.g. 'ID Number') since you're calling requestSort with the name. Add an id property to the objects in the TABLE_HEADERS array matching the property name (e.g. userNumber) in the data objects and pass it as an argument to both the requestSort and getClassNamesFor functions.
const TABLE_HEADERS = [
{ name: 'ID Number', id: 'userNumber' },
{ name: 'User Type', id: 'userType' },
{ name: 'User Category', id: 'errorId' },
{ name: 'User Interest', id: 'errorCategory' },
]
{
TABLE_HEADERS.map(({ name, id }) => (
<Th key={id}>
<Button
type="button"
onClick={() => requestSort(id)}
className={getClassNamesFor(id)}
>
{name}
</Button>
</Th>
))
}
You're also trying to use the description and isNumeric values from the header object but they are both undefined. You might want to add those properties to the objects in the TABLE_HEADERS array.
I have tried different things to eliminate Encountered two children with the same key, NaN. Keys should be unique and this is partial log:
in DynamicFields (at TableHeader.js)
in th (at TableHeader.js)
in TableHeader
Which is basically this bit of the TableHeader.js component, the full code is pasted lower down on this page:
return (
<th key={cleanTitle + - + index}
ref={(th) => th = th}
style={{width:width}}
data-col={cleanTitle}
>
<span className="header-cell" key={index*11}>{title} </span>
<DynamicFields key={header.index+title} parentIndex={(index + 3) + title} />
</th>
);
I have read through this discussion about keys and reactjs , followed it but still the error did not stop.
Here are the 3 component involved in rendering the datatable:
MyDatatable.js
import React from "react";
import TableHeader from "./TableHeader";
const MyDatatable = (props) => {
columnHeaders = [
{title: "Id" , accessor: 'id' , index: 0},
{title: "Name" , accessor: 'name', width: "300px", index: 2}
]
rowData = [
{id: 1, name: 'a', age: 29, qualification: 'B.com', rating: 3, profile: 'ps'},
{id: 2, name: 'b', age: 35, qualification: 'B.Sc', rating: 5, profile: 'h'}
]
const [headers, setHeaders] = React.useState(columnHeaders);
const [data, setData] = React.useState(rowData)
const renderContent = () => {
let contentView = data.map((row, rowIdx) => {
let id = row[keyField];
let tds = headers.map((header, index) => {
let content = row[header.accessor];
return (
<td key={index} data-id={id} data-row={rowIdx}>
{content}
</td>
);
});
return (
<tr key={rowIdx}>
{tds}
</tr>
);
//
}); //closes contentView variable
return contentView;
}
const renderTable = () => {
let title = props.title || "DataTable";
let contentView = renderContent();
return (
<table className="data-inner-table table-responsive">
<caption className="data-table-caption">
{title}
</caption>
<thead>
<tr>
<TableHeader headers={headers} />
</tr>
</thead>
<tbody>
{contentView}
</tbody>
</table>
)}
return (
<React.Fragment>
<div className={props.className}>
{renderTable() }
</div>
</React.Fragment>
)
}
TableHeader.js
import React from "react";
import DynamicFields from "../DynamicFields";
const TableHeader = (props) => {
let FieldTypes = ["text", "dropdown"];
const renderTableHeader = () => {
headers.sort((a, b) => {
if (a.index > b.index) return 1;
return -1
});
const headerView = headers.map((header, index) => {
let title = header.title;
let cleanTitle = header.accessor;
let width = header.width;
return (
<th key={cleanTitle + - + index}
ref={(th) => th = th}
style={{width:width}}
data-col={cleanTitle}
>
<span className="header-cell" key={index*11}>{title} </span>
<DynamicFields key={header.index+title} parentIndex={(index + 3) + title} />
</th>
);
} );
return headerView;
}
return(
<React.Fragment>
{renderTableHeader()}
</React.Fragment>
)
}
DynamicFields.js
import React, { useState, useEffect, useRef } from "react"
const DynamicFields = (props) => {
const optionsHash = ['Checkbox', 'Dropdown', 'boolean', 'Single line text'];
const [showDynamicField, setShowDynamicField ] = useState(false);
// const dropdownRef = useRef();
const handleShowDynamicField = (event) => {
setShowDynamicField(!showDynamicField);
};
return (
<React.Fragment>
<i className="bi bi-chevron-compact-down" onClick={handleShowDynamicField}></i>
{showDynamicField &&
optionsHash.map( (val, idx) => {
return(
<li key={val-idx} value={val} className="dropdown-item"> {val} </li>
)
})
}
</React.Fragment>
)
}
- is the subtraction operator, which will cause problems with key={val-idx} (string - number => NaN).
Presumably you want to use - as a separator character, so you'd use it as a string: key={val + '-' + idx} or key={`${val}-${idx}`}
In this case, since optionsHash has all unique strings, you could get away with just using key={val}.
The reason key={cleanTitle + - + index} works is because it evaluates to cleanTitle + (- +index), adding a negative number to a string which is allowed (but confusing).
I'm new to the react and I would like to know I made a function that makes filtering depending on the boolean values
let [filterState, setFilterSTate] = useState("all");
let filteredUser = todos.filter((е) => {
if (filterState === "active") {
return !е.done;
}
if (filterState === "completed") {
return е.done;
}
return true;
});
in the console, everything is displayed, but I can't figure out how to make it work in the browser itself and only the necessary tricks are displayed
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./index.css";
const TodoList = () => {
const [newTodo, setNewTodo] = useState("");
const [todos, setTodos] = useState([
{ done: true, text: "Hey", id: 1 },
{ done: false, text: "There", id: 2 },
{ done: false, text: "Dima", id: 3 },
]);
const [id, setId] = useState(4);
const toggleDone = (id) => {
setTodos(
todos.map((todo) => ({
...todo,
done: id === todo.id ? !todo.done : todo.done,
}))
);
};
const updateTodo = (id, e) => {
setTodos(
todos.map((todo) => ({
...todo,
text: id === todo.id ? e.target.value : todo.text,
}))
);
};
const onDelete = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const updateNewTodo = (e) => {
setNewTodo(e.target.value);
};
const onAdd = () => {
setTodos([
...todos,
{
text: newTodo,
done: false,
id,
},
]);
setId(id + 1);
setNewTodo("");
};
let [filterState, setFilterSTate] = useState("all");
let filteredUser = todos.filter((е) => {
if (filterState === "active") {
return !е.done;
}
if (filterState === "completed") {
return е.done;
}
return true;
});
// let filteredComplited = todos.filter(function (e) {
// return e.done === true;
// });
// console.log("filteredComplited", filteredComplited);
// let filteredActive = todos.filter(function (e) {
// return e.done === false;
// });
// console.log("filteredActive", filteredActive);
// let filteredAll = todos;
// console.log("filteredAll", filteredAll);
return (
<div className="todos">
Todo List
{todos.map((todo) => (
<div key={todo.id} className="todo">
<input
type="checkbox"
value={todo.done}
onChange={() => toggleDone(todo.id)}
/>
<input
type="text"
value={todo.text}
onChange={(evt) => updateTodo(todo.id, evt)}
/>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
))}
<div className="todo">
<input type="text" value={newTodo} onChange={updateNewTodo} />
<button onClick={() => onAdd()}>Add</button>
</div>
<button
onclick={() =>
todos.map((t) => `${t.done ? "x" : ""} ${t.text}`).join("\n")
}
>
Save
</button>
<button onClick={() => setFilterSTate("completed")}>Complited</button>
<button onClick={() => setFilterSTate("active")}>Active</button>
<button onClick={() => setFilterSTate("all")}>All</button>
<pre>filterState: {JSON.stringify(filterState, null, 2)}</pre>
<br />
<pre>{JSON.stringify(filteredUser, null, 2)}</pre>
</div>
);
};
ReactDOM.render(<TodoList />, document.getElementById("todos"));
My current implementation:
export const SORT_ORDER = {
ASC: "ascending",
DESC: "descending",
OTHER: "other"
};
export default class Table extends React.Component {
constructor(props) {
super(props);
this.sortIcon = new Map();
this.sortIcon.set(SORT_ORDER.ASC, sortAsc);
this.sortIcon.set(SORT_ORDER.DESC, sortDesc);
this.sortIcon.set(SORT_ORDER.OTHER, sortOther);
this.state = {
sortField: this.props.defaultSortColumn,
sortOrder: this.props.defaultSortOrder
};
}
componentDidMount() {
this.sort(this.props.defaultSortColumn, this.props.defaultSortOrder)();
}
retrieveOrder = (columnId) => {
return columnId === this.state.sortField ? this.state.sortOrder : SORT_ORDER.OTHER;
};
nextOrder = (current) => {
if (current === SORT_ORDER.DESC) {
return SORT_ORDER.ASC;
} else if (current === SORT_ORDER.ASC) {
return SORT_ORDER.DESC;
} else {
return this.props.defaultSortOrder;
}
};
sort = (columnId, order) => () => {
let descriptor = this.props.structure[columnId];
let values = this.props.value.slice();
let orderFactor = order === SORT_ORDER.ASC ? 1 : -1;
values.sort((a, b) => {
let first = descriptor.render(a);
let second = descriptor.render(b);
return first > second ? orderFactor : first < second ? -orderFactor : 0;
});
this.setState({
sortField: columnId,
sortOrder: order
});
this.props.onSort(values);
};
renderHeader = (id, descriptor) => {
let order = this.retrieveOrder(id);
let iconSrc = this.sortIcon.get(order);
let nextOrder = this.nextOrder(this.retrieveOrder(id));
return (
<th key={id} className={descriptor.headStyle}>
<a href="#" aria-sort={order} onClick={this.sort(id, nextOrder)}>
<img src={iconSrc}/>
{descriptor.label}
</a>
</th>
);
};
render() {
return (
<table>
Table structure
</table>
);
}
}
Parent component declares it in next way:
<Table structure={this.tableHeader} value={this.state.tableValue} onSort={this.handleChange('tableValue')}
defaultSortColumn="created" defaultSortOrder={SORT_ORDER.DESC} />
The table value is defined in props as value. onSort is a function that changes the state of the parent component => it changes the table value. Also I have defaultSortColumn and defaultSortOrder to sort the table after it is filled.
The problem is that my table can be declared multiple times at the page.
So,
1) I'm not able to store the table value in its state. Should I?
2) How can I implement default sorting without using componentDidMount? With using current implementation default sorting occurred only once, when componentDidMount is invoked, but I have more than 1 <Table/> component at the page.
I tried use componentWillReceiveProps function, but it is also invoked when I change the <Table/> state in sort function. So I can't use it.
My final solution is:
export const SORT_ORDER = {
ASC: "ascending",
DESC: "descending",
OTHER: "other"
};
class TableRow extends React.Component {
render() {
return (
<tr>
{this.props.children}
</tr>
);
}
}
class TableHeader extends React.Component {
constructor(props) {
super(props);
this.sortIcon = new Map([
[SORT_ORDER.ASC, {icon: sortAsc, title: "Ascending"}],
[SORT_ORDER.DESC, {icon: sortDesc, title: "Descending"}],
[SORT_ORDER.OTHER, {icon: sortOther, title: "Unsorted"}]
]);
}
render() {
const {children, onClick, sortOrder} = this.props;
return (
<th>
{onClick ? (
<a href="#" aria-sort={sortOrder} onClick={onClick}>
<img src={this.sortIcon.get(sortOrder).icon} title={this.sortIcon.get(sortOrder).title} />
{children}
</a>
) : children}
</th>
);
}
}
export default class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
sortField: props.defaultSortColumn,
sortOrder: props.defaultSortOrder
};
}
retrieveOrder = (columnId) => {
return columnId === this.state.sortField ? this.state.sortOrder : SORT_ORDER.OTHER;
};
nextOrder = (current) => {
if (current === SORT_ORDER.DESC) {
return SORT_ORDER.ASC;
} else if (current === SORT_ORDER.ASC) {
return SORT_ORDER.DESC;
} else {
return this.props.defaultSortOrder;
}
};
sortedRows = () => {
let descriptor = this.props.structure.find(d => d.attribute === this.state.sortField);
let values = this.props.value.slice();
let orderFactor = this.state.sortOrder === SORT_ORDER.ASC ? 1 : -1;
return values.sort((a, b) => {
let first;
let second;
// null and undefined values should be changed to empty string
if (typeof a[descriptor.attribute] === "number" || typeof b[descriptor.attribute] === "number") {
first = a[descriptor.attribute] || "";
second = b[descriptor.attribute] || "";
} else {
first = descriptor.render(a) || "";
second = descriptor.render(b) || "";
}
return first > second ? orderFactor : first < second ? -orderFactor : 0;
});
};
renderHeaders = () => {
return this.props.structure.map((descriptor, id) => {
let header;
if (this.props.sortable) {
const order = this.retrieveOrder(descriptor.attribute);
const nextOrder = this.nextOrder(order);
header = (
<TableHeader key={id} onClick={() => {this.setState({sortField: descriptor.attribute, sortOrder: nextOrder})}}
sortOrder={order}>
{descriptor.label}
</TableHeader>
)
} else {
header = (
<TableHeader key={id}>
{descriptor.label}
</TableHeader>
)
}
return header;
});
};
renderRows = () => {
const Row = this.props.customRow || TableRow;
const values = this.props.sortable ? this.sortedRows() : this.props.value;
return values.map((value, idx) => (
<Row key={idx} value={value}>
{this.props.structure.map((descriptor, id) => (
<td key={id}>
descriptor.render(value, idx)
</td>
))}
</Row>
));
};
render() {
return (
<table className={this.props.className}>
<thead>
<tr>
{this.renderHeaders()}
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
);
}
}
Example of the table usage:
this.tableStructure = [
{
attribute: "number", label: "Row Number"
render: (row) => {return row.number}
},
{
attribute: "created", label: "Creation time"
render: (row) => {return this.dateToString(row.created)}
},
{
attribute: "type", label: "Row Type"
render: (row) => {return row.type}
},
{
attribute: "state", label: "State",
render: (row) => {return row.state}
},
{
attribute: "action", label: "Action"
render: (row) => {
return (
<button onClick={this.doSomething}>
</button>
);
}
}
];
<Table structure={this.tableStructure} value={this.state.someValue} sortable
defaultSortColumn="created" defaultSortOrder={SORT_ORDER.DESC} />
The implementation is based on http://styleguide.cfapps.io/react_base_tables.html