I have the following component. I used react hooks (useHistory, useState) in my component.
export default function ClassTheoryDataTable() {
const dataSource = [
{
key: '1',
date: '18.03.2021',
subject: 'Revision',
inst: 'HASHEL',
edit: 'edit',
delete: 'delete'
}
];
let history = useHistory();
const [tableData, setTableData] = useState(dataSource);
const handleRedirect = (data) => {
history.push("/ClassTheoryDetails");
};
const handleDelete = (key) => {
let dataSource1 = [...tableData];
dataSource1 = dataSource1.filter((item) => item.key !== key);
setTableData(dataSource1);
}
const columns = [
{
title: 'Date',
dataIndex: 'date',
key: 'date',
render: (text, record) => (
<a onClick={() => handleRedirect(record)} type="text">{text}</a>
)
},
{
title: 'Subject',
dataIndex: 'subject',
key: 'subject',
editable: true
},
{
title: 'Inst.',
dataIndex: 'inst',
key: 'inst',
editable: true
},
{
title: '',
dataIndex: 'edit',
key: 'edit',
width: '50px',
render: (text, record) => (
<Space size="middle">
<EditOutlined style={{ color: '#1589FF', fontSize: '15px' }} />
</Space>
)
},
{
title: '',
dataIndex: 'delete',
key: 'delete',
width: '50px',
render: (text, record) => (
dataSource.length >= 1 ?
<Popconfirm title="Sure to delete ?" onConfirm={() => handleDelete(record.key)}>
<CloseCircleFilled style={{ color: 'red', fontSize: '15px' }} />
</Popconfirm>
: null
)
}
];
return (
<>
<Table columns={columns} dataSource={tableData} pagination={false} bordered />
</>
);
}
Essentially I want to delete table row by clicking the delete icon in the last column. But, I'm getting the "Rendered more hooks than during the previous render" error when I load the page. I don't know how to fix it. Can someone help me?
The error is being thrown in the AntD component code, but is only manifesting because of how you've specified your table props.
The issue is in your ClassTheory component in the expandRowRender you are not instantiating the ClassTheoryDataTable subtable correctly.
const expandedRowRender = () => {
const table = new ClassTheoryDataTable();
return table;
};
Here you are invoking the function component directly and returning it, but this isn't how React components are instantiated. In React you describe the UI via JSX and pass this to React and React handles the entire component lifecycle, from instantiation, mounting, rerendering, and unmounting.
This function should return valid JSX.
const expandedRowRender = () => {
return <ClassTheoryDataTable />;
};
Related
I am new to NEXT JS, and facing this wierd issue for which there seems to be no solution on internet. I am getting this Warning: Each child in a list should have a unique "key" prop like so:
Warning: Each child in a list should have a unique "key" prop.
Check the top-level render call using <tbody>. See https://reactjs.org/link/warning-keys for more information.
at BodyRow (/home/anchal/development-backoffice/node_modules/rc-table/lib/Body/BodyRow.js:37:25)
at Body (/home/anchal/development-backoffice/node_modules/rc-table/lib/Body/index.js:41:19)
at Body (/home/anchal/development-backoffice/node_modules/rc-table/lib/Body/index.js:41:19)
at table
at div
at div
at /home/anchal/development-backoffice/node_modules/rc-table/lib/Table.js:126:23
at /home/anchal/development-backoffice/node_modules/rc-table/lib/Table.js:126:23
at div
at DomWrapper (/home/anchal/development-backoffice/node_modules/rc-resize-observer/lib/SingleObserver/DomWrapper.js:31:34)
at SingleObserver (/home/anchal/development-backoffice/node_modules/rc-resize-observer/lib/SingleObserver/index.js:27:24)
at ResizeObserver (/home/anchal/development-backoffice/node_modules/rc-resize-observer/lib/index.js:27:24)
at Table (/home/anchal/development-backoffice/node_modules/rc-table/lib/Table.js:141:25)
at div
at div
at Spin (/home/anchal/development-backoffice/node_modules/antd/lib/spin/index.js:100:37)
at SpinFC (/home/anchal/development-backoffice/node_modules/antd/lib/spin/index.js:226:34)
at div
at InternalTable (/home/anchal/development-backoffice/node_modules/antd/lib/table/Table.js:73:34)
at div
at Assets (webpack-internal:///./src/collections/Assets/Assets.jsx:22:19)
at div
at AssetsPage (webpack-internal:///./src/pages/dashboard/assets.jsx:28:23)
at SessionProvider (/home/anchal/development-backoffice/node_modules/next-auth/react/index.js:416:24)
at MyApp (webpack-internal:///./src/pages/_app.js:17:18)
at StyleRegistry (/home/anchal/development-backoffice/node_modules/styled-jsx/dist/stylesheet-registry.js:231:34)
at AppContainer (/home/anchal/development-backoffice/node_modules/next/dist/server/render.js:390:29)
at AppContainerWithIsomorphicFiberStructure (/home/anchal/development-backoffice/node_modules/next/dist/server/render.js:420:57)
at div
at Body (/home/anchal/development-backoffice/node_modules/next/dist/server/render.js:688:21)
I have been using .map with key attribute at several places, but to check the cause of the issue, I have had commented the instances where .map was used, but the issue still exists and I have no clue from where is it coming.
Below is my src/collections/Assets/Assets.jsx:
import { AddAssets } from ".";
import { Table, Space, Button } from "antd";
import { useState } from "react";
import { DeleteData } from "..";
import { OwnerModal } from "..";
export const Assets = ({ assets, setAssets, collection, users }) => {
const [asset, setAsset] = useState();
const [editMode, setEditMode] = useState(false);
const [showModal, setShowModal] = useState(false);
const [isModalVisible, setIsModalVisible] = useState(false);
const columns = [
{
title: "User Id",
key: "userId",
render: (_, record) => <OwnerModal data={record}></OwnerModal>,
},
{
title: "Title",
dataIndex: "title",
key: "title",
// sorter: (a, b) => a.firstName.localeCompare(b.firstName),
},
{
title: "Description",
dataIndex: "description",
key: "description",
},
{
title: "Land Registery",
dataIndex: "landRegistry",
key: "landRegistry",
render: (_, record) => (
<a
href={record?.landRegistry}
target="_blank"
rel="noopener noreferrer"
>
LandRegistry Image
</a>
),
},
{
title: "Floor Area",
dataIndex: "floorArea",
key: "floorArea",
},
{
title: "Has Outdoor Space",
key: "hasOutdoorSpace",
render: (_, record) => String(record.hasOutdoorSpace),
},
{
title: "Bedrooms",
dataIndex: "bedrooms",
key: "bedrooms",
},
{
title: "Bathrooms",
dataIndex: "bathrooms",
key: "bathrooms",
},
{
title: "Other Rooms",
dataIndex: "otherRooms",
key: "otherRooms",
},
{
title: "Sale Time Frame",
dataIndex: "saleTimeframe",
key: "saleTimeframe",
},
{
title: "Extra Conditions Labels",
dataIndex: "extraConditionsLabels",
key: "extraConditionsLabels",
},
{
title: "Extra Conditions Descriptions",
dataIndex: "extraConditionsDescriptions",
key: "extraConditionsDescriptions",
},
{
title: "AVM",
// dataIndex: "AVM",
key: "AVM",
render: (_, record) => (
<a href={record?.AVM} target="_blank" rel="noopener noreferrer">
AVM
</a>
),
},
{
title: "Survey Proof",
dataIndex: "surveyProof",
key: "surveyProof",
render: (_, record) => (
<a href={record?.surveyProof} target="_blank" rel="noopener noreferrer">
Survey Proof
</a>
),
},
{
title: "Images",
dataIndex: "images",
key: "images",
render: (_, record) =>
record.images.map((imageUrls, index) => (
<div key={index}>
<a href={imageUrls} target="_blank" rel="noopener noreferrer">
Image {index + 1}{" "}
</a>
</div>
)),
},
{
title: "Action",
key: "action",
render: (_, record) => (
<Space size="middle">
<Button
onClick={() => {
setAsset(record);
setEditMode(true);
setShowModal(true);
}}
>
Edit
</Button>
<DeleteData
users={assets}
setUsers={setAssets}
collection={collection}
data={record}
/>
</Space>
),
},
];
const dataSource = assets;
return (
<>
<AddAssets
users={users}
assets={assets}
editMode={editMode}
setEditMode={setEditMode}
asset={asset}
setAsset={setAsset}
showModal={showModal}
setShowModal={setShowModal}
setAssets={setAssets}
collection={collection}
/>
<div className="mt-8">
<Table
scroll={{ x: true }}
dataSource={dataSource}
columns={columns}
pagination={{
position: ["bottomCenter"],
pageSize: 20,
showSizeChanger: true,
hideOnSinglePage: true,
}}
/>
</div>
</>
);
};
And here comes src/pages/dashboard/assets.jsx:
import { prisma } from "~/prisma";
import Head from "next/head";
import { Navbar, Heading } from "~/components";
import { Users } from "~/collections";
import { useState } from "react";
import { Assets } from "src/collections/Assets";
import axios from "axios";
const AssetsPage = ({ collectionName, data, users }) => {
const [assets, setAssets] = useState(data);
return (
<>
<Head>
<title>Backoffice - Assets</title>
</Head>
<Navbar currentPage="asset" />
<div className="container mx-auto p-8 text-center">
<Heading level={3}>Assets</Heading>
<Assets
users={users}
assets={assets}
// setAssets={setAssets}
collection={collectionName}
/>
</div>
</>
);
};
export default AssetsPage;
export const getServerSideProps = async () => {
const { data } = await axios.get(
process.env.NEXTAUTH_URL + `/api/asset/get/getAsset`
);
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
},
});
return {
props: {
data,
users,
collectionName: "asset",
},
};
};
rowKey="id needs to be added in antd table to resolve this error.
<Table
dataSource={dataSource}
columns={columns}
rowKey="id"
/>
I am using Ant Design for my React project and I'm having trouble with the Table component. I have a list of tasks to which I add a new task based on a Form content - currently just by adding to an array of objects (taskListMock in the code snippets), the app is not linked to any backend. The form works fine, however, the Table does not refresh, even though the dataSource prop of the Table gets its content directly from the state and the state updates correctly - confirmed by logging and devtools. Curiously, the table refreshes with the new task when I initiate the implemented sorting, so my suspicion is that the Table somehow does not refresh its content from the state change, only on onChange hooks or something, but I'm feeling in a bit of a dead-end - any help would be greatly appreciated since I'm planning to use similar functionality in other Tables.
The structure is pretty simple, I have a TasksIndex.js with the Table as an individual component in TaskListTable.js
TaskListTable.js:
const TaskListTable = (props) => {
const { t } = useTranslation();
const [tableContent, setTableContent] = useState(props.tasks);
return (
<React.Fragment>
<Table
pagination={false}
dataSource={tableContent}
columns={[
{
title: t("tasks.name"),
key: "name",
render: (text) => {
return <p>{text.slug}</p>;
},
},
{
title: t("tasks.dateDue"),
dataIndex: "dateDue",
key: "dateDue",
sorter: (a, b) =>
new Date(a.dateDue).getTime() - new Date(b.dateDue).getTime(),
render: (dateDue) => {
let dateFormatted = moment(dateDue);
return <>{dateFormatted.format("LL")}</>;
},
defaultSortOrder: "ascend",
},
{
title: t("tasks.priority"),
key: "priority",
dataIndex: "priority",
render: (priority) => (
<React.Fragment>
{priority === "low" ? (
<Tag color="geekblue">{t("tasks.lowPriority")}</Tag>
) : (
""
)}
{priority === "normal" ? (
<Tag color="green">{t("tasks.normalPriority")}</Tag>
) : (
""
)}
{priority === "high" ? (
<Tag color="volcano">{t("tasks.highPriority")}</Tag>
) : (
""
)}
</React.Fragment>
),
sorter: (a, b) => {
const priorityOrder = ["low", "normal", "high"];
return (
priorityOrder.indexOf(a.priority) -
priorityOrder.indexOf(b.priority)
);
},
},
{
title: t("tasks.options"),
key: "options",
render: (item) => {
return (
<Checkbox value={item.id}>{t("tasks.setCompleted")}</Checkbox>
);
},
},
]}
></Table>
</React.Fragment>
);
};
export default TaskListTable;
TaskIndex.js:
const TasksIndex = () => {
const [isModalOpen, setModalOpen] = useState(false);
const [taskList, updateTaskList] = useState(taskListMock);
const [form] = Form.useForm();
const addTask = useCallback(
(values) => {
const newTaskList = taskList;
newTaskList.push({
id: taskList[taskList.length - 1] + 1,
slug: values.name,
description: values.description,
dateDue: values.dateDue.format("YYYY-MM-DD"),
priority: values.priority,
checked: false,
});
form.resetFields();
updateTaskList(newTaskList);
closeModal();
},
[taskList, form]
);
const openModal = () => {
setModalOpen(true);
};
const closeModal = () => {
setModalOpen(false);
};
const { t } = useTranslation();
return (
<React.Fragment>
<Title>{t("tasks.tasksOverviewHeader")}</Title>
<Row gutter={[16, 24]}>
<Col className="gutter-row" span={24}>
<TaskListTable tasks={taskList}></TaskListTable>
</Col>
</Row>
...
...
I finally fixed it - it seems that creating a new array and pushing the new task to it was not considered a state change (or perhaps a Table change trigger), unlike using the spread operator. The working code looks like this:
const addTask = (values) => {
const newTask = {
id: taskList[taskList.length - 1] + 1,
slug: values.name,
description: values.description,
dateDue: values.dateDue.format("YYYY-MM-DD"),
priority: values.priority,
checked: false,
};
updateTaskList([...taskList, newTask]);
closeModal();
form.resetFields();
};
I'm struggling with something that should be more obvious to me.
when someone clicks on a antd popconfirm's "yes" option, it's supposed to trigger the onConfirm function and then update a record in my redux store via a dispatch action. All I want to do is to change one field (archived) from false to true in this record. I know that props are immutable so i can't just change the prop. But how to approach this?
I vaguely recall a way to pass a record on and modify a field using a spread operator? is there some easy way to do this? Or do i need to somehow convert my prop object to state so i can modify it and pass it on somehow?
I map my selector via the following.. AFAIK I just need to pass the id of the property i care about along with the object that i want it to replace:
const mapDispatchToProps = (dispatch) => ({
archiveProperty: (id, property) => dispatch(startEditProperty(id, property))
});
The antd popconfirm block that calls onConfirm. Probably not terrible interesting:
{
title: 'Action',
key: 'action',
render: (text, record) => (
<span>
<a>Edit</a>
<Divider type="vertical" />
<Popconfirm
title="Are you sure?"
onConfirm={() => this.confirm(record)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
Archive
</Popconfirm>
</span>
),
},
Here is where I think my problem is. It works but i am currently only passing the existing record in. Not a modified version of it. How to pass a version of it that has it's archived field set to true?
confirm = (record) => {
message.success('Archived');
console.log("confirm function.. record");
this.props.archiveProperty(record.id, record);
}
The entire file if it's useful looks like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import selectProperties from '../selectors/properties';
import { startEditProperty } from '../actions/properties';
import { Table, Tag, Divider, Popconfirm, message } from 'antd';
export class PropertyList extends React.Component {
constructor(){
super();
this.columns = [
{
title: 'Address',
dataIndex: 'street',
key: 'street',
render: text => <a>{text}</a>,
},
{
title: 'City',
dataIndex: 'city',
key: 'city',
},
{
title: 'State',
dataIndex: 'state',
key: 'state',
},
{
title: 'Workflow',
key: 'workflow',
dataIndex: 'workflow',
sorter: (a, b) => a.workflow.length - b.workflow.length,
sortDirections: ['descend'],
render: workflow => {
let color = 'geekblue';
if (workflow === 'Inspection' || workflow === 'Maintenance' || workflow === 'Cleaning') {
color = 'volcano';
}
else if (workflow === 'Rented') {
color = 'green';
}
return (
<span>
<Tag color={color} key={workflow}>
{workflow.toUpperCase()}
</Tag>
</span>
);
},
},
{
title: 'Action',
key: 'action',
render: (text, record) => (
<span>
<a>Edit</a>
<Divider type="vertical" />
<Popconfirm
title="Are you sure?"
onConfirm={() => this.confirm(record)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
Archive
</Popconfirm>
</span>
),
},
];
}
confirm = (record) => {
message.success('Archived');
console.log(record);
this.props.archiveProperty(record.id, record);
}
cancel = () => {
message.error('Cancelled');
console.log("cancel function..");
}
render() {
return (
<div className="content-container">
<div className="list-body">
{
this.props.properties.length === 0 ? (
<div className="list-item list-item--message">
<span>No properties. Add some!</span>
</div>
) : (
<Table
rowKey="id"
dataSource={this.props.properties}
columns={this.columns}
pagination = { false }
footer={() => ''}
/>
)
}
</div>
</div>
)
}
};
const mapStateToProps = (state) => {
console.log("PropertyList mapStateToProps..");
console.log(state);
return {
properties: selectProperties(state.properties, state.filters)
};
};
const mapDispatchToProps = (dispatch) => ({
archiveProperty: (id, property) => dispatch(startEditProperty(id, property))
});
export default connect(mapStateToProps, mapDispatchToProps)(PropertyList);
You should copy the record object and change its one field (archived) from false to true in copied object.
Try this in your confirm method.
confirm = (record) => {
message.success('Archived');
console.log("confirm function.. record");
// create a new modified object
const updatedRecord = Object.assign({}, record, {archived: true});
//now pass this updatedRecord object as a new record to store
this.props.archiveProperty(record.id, updatedRecord);
}
And in reducer just replace old record object with this updatedRecord object.
Hope it helps.
i have a problem with my actionsFormatter.
When I click on the DELETE button, I get the error:
Uncaught ReferenceError: dispatch is not defined at onClick
How could I fix this problem?
import { removeEnvironnement } from '../../actions/environnement';
const EnvironnementList = (props) => (
<BootstrapTable
keyField='id'
data={ props.store.environnements }
columns={ columns }
selectRow={selectRow}
pagination={ paginationFactory() }
filter={ filterFactory() }
striped hover condensed
/>
);
const actionsFormatter = (cell, row) => {
const id=row.id
return (
<button className="btn btn-danger"
onClick={() => {
dispatch(removeEnvironnement({ id }));}}
>Delete</button>
);
};
const columns = [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'nom',
text: 'Nom',
filter: textFilter()
}, {
dataField: 'actions',
text: 'Action',
formatter: actionsFormatter
} ];
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
bgColor: '#00BFFF'
};
const mapStateToProps = (state) => {
return {
store: state
};
}
export default connect(mapStateToProps)(EnvironnementList);
Here is my code to do the remove :
Should I remove the dispatch part?
const _removeEnvironnement = ({ id } = {}) => ({
type: 'REMOVE_ENVIRONNEMENT',
id
});
export const removeEnvironnement = ({ id } = {}) => {
return (dispatch) => {
return axios.delete(`environnements/${id}`).then(() => {
dispatch(_removeEnvironnement({ id }));
})
}
};
What is dispatch in your actionsFormatter? It is defined neither on actionsFormatter scope nor on out of actionsFormatter scope. That's the problem and that's the javascript interpreter talking you about.
One of the possible fix is to import you redux store
store.js
export const store = createStore(...)
EnvironmentList.js
import { store } from './path/to/store.js'
// ...
const actionsFormatter = (cell, row) => {
const { dispatch } = store
const id = row.id
// ...
};
This way you'll get dispatch available in actionsFormatter body.
Another way is to provide mapped method via connect -> EnvironmentList -> actionsFormatter chain. Do what Arnaud Christ suggested in his reply and then refactor the code:
const EnvironmentList = (props) => (
<BootstrapTable
keyField='id'
data={ props.store.environnements }
columns={ columns(props.removeEnvironment) }
selectRow={selectRow}
pagination={ paginationFactory() }
filter={ filterFactory() }
striped hover condensed
/>
);
const actionsFormatter = (removeEnvironment) => (cell, row) => {
const id=row.id
return (
<button className="btn btn-danger"
onClick={() => {
removeEnvironment({ id });
}}
>Delete</button>
);
};
const columns = (removeEnvironment) => [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'nom',
text: 'Nom',
filter: textFilter()
}, {
dataField: 'actions',
text: 'Action',
formatter: actionsFormatter(removeEnvironment)
} ];
So, the connected EnvironmentList got necessary removeEnvironment method on it's props. Then we passes it to columns creator, which passed it to actionsFormatter creator.
You have to link your component with the dispatch method.
As you are already using react-redux to connect your component to your Redux store, you can easily do that through mapping dispatch to props.
Just add the following:
const mapStateToProps = (state) => {
return {
store: state
};
}
const mapDispatchToProps = dispatch => {
return {
removeEnvironnement: id => {
dispatch(removeEnvironnement({ id }));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(EnvironnementList);
And then in your onClick handler, just call this.props.removeEnvironnement(id)
I have a react-table in my react app which renders rows of transactions. I have logic that adds a new, empty row on a button click. I want this row (and only this row) to be editable so that I can use it to create new transactions. I've tried to accomplish this using the Cell column property and the row index from the cellInfo. This works to make only the single column that I'm testing with editable, but the rest of the cells in that column are blank.
import React from 'react';
import axios from 'axios';
import ReactTable from "react-table";
import "react-table/react-table.css";
const API = 'http://127.0.0.1:8000/api/transactions/';
class Transactions extends React.Component {
constructor() {
super();
this.state = {
data: [],
isLoading: true,
error: null
};
this.fetchData = this.fetchData.bind(this);
this.handleAddTransaction = this.handleAddTransaction.bind(this);
this.renderEditable = this.renderEditable.bind(this);
}
handleAddTransaction() {
let newEmptyRow = {
date: "",
payee: "",
category: "",
amount: ""
}
const insert = (arr, index, newItem) => [ ...arr.slice(0, index), newItem, ...arr.slice(index) ];
console.log(newEmptyRow);
this.setState(currentState => ({ data: insert(currentState.data, 0, newEmptyRow) }));
}
fetchData(state, instance) {
this.setState({
isLoading: true
});
axios.get(API).then(response => {
this.setState({
data: response.data.results,
isLoading: false
});
});
}
renderEditable(cellInfo) {
if(cellInfo.row._index === 0) {
return (
<div
style={{ backgroundColor: "#009999" }}
contentEditable
suppressContentEditableWarning
onBlur={e => {
const data = [...this.state.data];
data[cellInfo.index][cellInfo.column.id] = e.target.innerHTML;
this.setState({ data });
}}
dangerouslySetInnerHTML={{
__html: this.state.data[cellInfo.index][cellInfo.column.id]
}}
/>
);
}
}
render() {
const {
data,
isLoading
} = this.state;
return (
<div>
<button onClick={this.handleAddTransaction} type="button">New +</button>
<div>
<ReactTable
columns = {[
{
Header: 'Date',
accessor: 'date',
width: 200,
Cell: this.renderEditable
},
{
Header: 'Payee',
accessor: 'payee',
width: 400
},
{
Header: 'Category',
accessor: 'category',
width: 200
},
{
Header: 'Amount',
accessor: 'amount',
width: 200
},
{
Header: 'Balance',
accessor: 'balance',
width: 200
}]}
data = {data}
onFetchData={this.fetchData} // Request new data when things change
defaultPageSize = {500}
sortable = {false}
manual
style={{
// This will force the table body to overflow and scroll
// TODO Finalize height
height: "800px"
}}
loading={isLoading}
className="-striped -highlight"
/>
</div>
</div>)
};
}
export default Transactions;