useEffect(() => {
axios("http://localhost:8080/all")
.then((res) => {
setData(res.data);
})
.catch((err) => console.log(err))
}, []);
For one of my column headers I have:
const columns = useMemo(
() => [
{
Header: "Stock search",
columns: [
{
Header: "Symbol",
accessor: "symbol",
Cell: (props) => (
<a href={`/code/${props.value}`}>{props.value}</a>
),
},
And just a table that uses the useTable hook with React-Table-v7 (not tanstack) that returns this:
return (
<div className="Stocks">
<Table columns={columns} data={data} />
</div>
);
All I'm trying to do is: when the user clicks on any "symbol" value in the table, it updates the table and new columns appear, with data based on that symbol that was clicked which I already have in my external API link above. So the default columns are: "Symbol | Name | Field", and if you click on any symbol, it updates the table with relevant data and only the columns "Date | Volumes | Type" appear. I have tried so many variations like ag-grid, material-table, even using html only but I don't know what else to do here.
Related
I have an API which has a bunch of data in it. I should be able to click the first column with the header "Symbol" which then goes to a completely new, updated table. This is how it looks before the cell click. This is what happens though after I click a cell. The page goes blank even though the URL has updated based on my code. What I want to happen is after the cell click, the table updates with new column headers and row values, based on the "symbol" I clicked, with data filling the table from the API.
When I click on a cell, it goes to a brand new page based on that cell (so like /history/A), but how am I supposed to create a .js file for 500 different cell values?
I am using a useTable hook with react-table-v7, and I have a separate Table.js file for the layout.
function Stocks() {
const [data, setData] = useState([]);
useEffect(() => {
axios(`http://localhost:3000/all`)
.then((res) => {
setData(res.data);
})
.catch((err) => console.log(err))
}, []);
const columns = useMemo(
() => [
{
Header: "Stock search",
columns: [
{
Header: "Symbol",
accessor: "symbol",
Cell: ({ row }) => (<Link to={{ pathname: `/history/${row.original.symbol} `}}>{row.original.symbol}</Link>)
},
{
Header: "Name",
accessor: "name",
},
{
Header: "Industry",
accessor: "industry",
},
],
},
],
[]
)
return (
<div className="Stocks">
<Table
columns={columns}
data={data}
/>
</div>
);
}
I have no errors, warnings or issues on my dev tools either.
I'm trying to create a table component that can display its cells flexibly. Every cells on a column displays the same cell component. The table component take an array of object as initial data and an array of column properties. Table should looks like this:
interface MyTableProps {
columns: ColumnProps[];
initialData: Array<any>; // Should array of object
callbackTableDataChanged: (newData) => void; // being used to send modified table data back to MyTable's father components.
}
interface ColumnProps {
label: string;
objectPropertyName: string; // (*)
renderCell: "cell-type-1" | "cell-type-2" | "cell-type-3" | ... // (**)
}
const MyTable: React.FC<MyTableProps> = tableProps => {
// The table should have its own a copy of initial data, because of many reasons
const [tableData, setTableData] = useState(tableProps.initialData);
const handleDataChange = (thePropertyName: string, rowIndex: number, newValue: any) => {
// This function changes the table data array,
// whenever the cell component (MyCellComp1, MyCellComp2, ...) on a column make a change of value.
// For example: MyCellComp1 is a time picker, whenever a new timestamp selected,
// it will send new time value through a callback, to its father component (aka MyTable).
// Then, this func will do the changes to the corresponding "place" in the data array object of this MyTable.
}
const renderRows = (column: ColumnProps, rowIndex: number) => {
switch (column.renderCell) {
case cell-type-1: return <CellComp1 ... onDataChanged={cellData => handleDataChange(column.objectPropertyName, rowIndex, cellData)}/>
case cell-type-2: return <CellComp2 ... onDataChanged={cellData => handleDataChange(column.objectPropertyName, rowIndex, cellData)}/>
...
}
}
return (
{renderColumnLabels}
<Flatlist data={tableData} renderItem={({col, index}) => renderRows(col, index)}/>
)
}
(*): "objectPropertyName" is a string that has value which is a name of one of the properties of initial object data. Forgive my awkward grammar!
(**): I use "renderCell" to tell the table what it should render at specific column. All cells on a column have the same component type.
The table should be used like this:
const initialData = [
{id: "123", name: "Tom"}, {id: "456", name: "Jerry"}, ...
]
const columns: ColumnProps[] = [
{label: "The ID", objectPropertyName: "id", renderCell: "cell-type-1"},
{label: "The name", objectPropertyName: "name", renderCell: "cell-type-2"},
...
];
<MyTable columns={columns} initialData={initialData} ... />
// This is how table looks like:
// column 1 column 2
// labels row: The ID The name
// row 1 : <CellComp1 /> <CellComp2 />
// row 2 : <CellComp1 /> <CellComp2 />
// ...
The problem is that with above approach, with the way I declared how cells on a column should be rendered, it limits the variety of cell component that MyTable could display. I declare 3 values for ColumnProps.rendercell, columns can display only 3 kinds of cell components.
My solution is that ColumnProps will have a new props which is a function that return a component:
interface ColumnProps {
label: string;
objectPropertyName: string; // (*)
renderCell: "cell-type-1" | "cell-type-2" | "cell-type-3" | ... // (**)
renderCellComponent?: () => JSX.Element
}
The new "renderCellComponent" prop will replace default cell components with whatever it returns.
However, with is method, I cant change the data array of MyTable with its function "handleDataChange".
For example:
const initialData = [
{id: "123", name: "Tom"}, {id: "456", name: "Jerry"}, ...
]
const columns: ColumnProps[] = [
{label: "The ID", objectPropertyName: "id", renderCell: "cell-type-1", renderCellComponent: () => <NewCellComp1 ... />},
{label: "The name", objectPropertyName: "name", renderCell: "cell-type-2", renderCellComponent: () => <NewCellComp2 ... />},
...
];
<MyTable columns={columns} initialData={initialData} ... />
As you can see, NewCellComp1 and NewCellComp2 need to access the function "handleDataChange" of MyTable just like the way CellComp1 and CellComp2 did. In order to do that, I tried to use React.forwardRef and useImperativeHandle to MyTable:
const MyTable: React.FC<MyTableProps> = React.forwardRef((tableProps, ref) => {
...
useImperativeHandle(ref, () => {
refFuncHandleDataChange(objectPropertyName: string, rowIndex, newValue) {
handleDataChange(objectPropertyName, rowIndex, newValue);
}
})
return ...
})
Now, MyTable should be used like this:
const initialData = [
{id: "123", name: "Tom"}, {id: "456", name: "Jerry"}, ...
]
const columns: ColumnProps[] = [
{...other props, objectPropertyName: "id", renderCellComponent: () => <NewCellComp1 onDataChanged={data => refTable.current?.refFuncHandleDataChange("id", ..., data)} />},
{...other props, objectPropertyName: "name", renderCellComponent: () => <NewCellComp2 onDataChanged={data => refTable.current?.refFuncHandleDataChange("name", ..., data)} />},
...
];
const refTable = useRef();
<MyTable ref={refTable} columns={columns} initialData={initialData} ... />
As you can see, with this approach, I still lack the row index of the cell component that make a change of data. When cell rendering is declared inside MyTable, I can access to row index easily thanks to Flatlist, but outside MyTable does not offer that luxury, because "renderCellComponent" function of ColumnProps is a declaration for all cells on a column. This is where I'm stuck.
In conclusion, I want to create a table component that be able to display every kind of components on its cells. Also, keep its properties as "simple" as possible. You can see that I'm currently must declare only data and columns props. But my approach seems to be impossible to do that.
Can you share me an improvement for what I did or an entirely new approach for this problem. Thank you!
So I have a user drop down menu that allows a person to impersonate a user to get a list of buckets. If you pick a different user it then makes an api call to then get a new list of buckets based upon that user. I am using react-table to create a table with information about the user's buckets. I can change the user on the other pages and the user gets updated and nothing explodes. But if I do it on the bucket page, it returns this:
The API call which happens in the parent component of my bucketTable component in the useEffect hook and that is tied to the user that I drop down from the app.js. The API call fires off and returns a list of buckets which I then pass down to my bucketTable component which then takes it and inserts it into a react-table.
I have an onChange event that bubbles up to App.js from my navbar and then drop the selected user down to the buckets page. Which then does an API call using useEffect which is then tied to the user field that I am passing around.
bucketTable.js
so the issue is that when I change the userDropdown on the navbar which then updates that value in the buckets.js component to get the list of buckets, this table page gets overwhelmed. I don't understand why I see my use effect is firing only when user is fired. I have done console.log statements and see that it only gets fired when I change it once, but this happens. This makes me think that something is rerendering over and over again causing this issue. Please help been pulling my hair out on this and I can provide more details upon request.
EDIT:
So when I change the user ON the bucket page which renders the bucketTable component I see that the bucket Table component is being rerendered a billions times...still looking into this will update when I have more information.
this is my bucketTable.js which is being rendered a TON when I change the user.
import "../index.css";
import { useTable } from "react-table";
const BucketTable = ({ buckets }) => {
console.log('BucketTable Rendered')
const data = []
const columns = [
{
Header: "Bucket Name",
accessor: "bucket",
},
{
Header: "Created By",
accessor: "created_by",
},
{
Header: "Storage Label",
accessor: "storage_label",
},
{
Header: "Zone",
accessor: "zone",
}
];
// Must only get the array cleansing of objs
buckets.buckets.forEach((x) => data.push(x));
console.log(data)
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable({
columns,
data
});
return (
<>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.value}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.value}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</>
);
};
export default BucketTable;
buckets.js which calls the bucketTable.js component
import { useEffect, useState } from "react";
import BucketList from "../Components/BucketList";
import useCollapse from "react-collapsed";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import * as AiIcons from "react-icons/ai";
import BucketTable from "../Components/BucketTable";
// TODO: Shift from cards to table
const Buckets = ({ user }) => {
const [bucketList, getBuckets] = useState(null);
const { getCollapseProps, getToggleProps, isExpanded } = useCollapse();
const [selectedACL, setValue] = useState("private");
const callGetBuckets = () => {
console.log("callGetBuckets Called")
const headers = {
"Content-Type": "application/json",
user: user,
};
notify("loading");
fetch("http://localhost:5000/rest/v1/buckets", { headers })
.then((res) => {
return res.json();
})
.then((data) => {
console.log('fired off?')
getBuckets(data);
notify("dismiss");
});
};
useEffect(() => {
console.log("useEffect fired")
callGetBuckets();
}, [user]);
const notify = (type) => {
switch (type) {
case "success":
toast.success("Bucket Created", {
toastId: "Success",
});
break;
case "fail":
toast.error("Bucket Creation Failed", {
toastId: "fail",
});
break;
case "loading":
toast.loading("Loading...", {
toastId: "loading",
});
break;
case "dismiss":
toast.dismiss();
break;
default:
}
};
const handleSubmission = (e) => {
e.preventDefault();
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json", user: user },
body: JSON.stringify({
bucket: e.target.Name.value,
storage_label: e.target.StorageLabel.value,
acl: selectedACL,
}),
};
fetch("http://localhost:5000/rest/v1/bucket/create", requestOptions).then(
(res) => {
if (res.ok) {
notify("success");
console.log('fired off as well')
callGetBuckets();
} else notify("fail");
}
);
};
return (
<div>
<ToastContainer theme="theme" />
<div className="content">
<h2 className="bucketHeader">Buckets</h2>
<div>
<p>Returns a list of buckets that a user has access to view.</p>
</div>
<div className="collapsible">
<div className="header" {...getToggleProps()}>
Create Bucket
<span>
{isExpanded ? (
<AiIcons.AiOutlineArrowUp />
) : (
<AiIcons.AiOutlineArrowDown />
)}
</span>
</div>
<div {...getCollapseProps()}>
<div className="content-collapsible">
<form className="createBucketForm" onSubmit={handleSubmission}>
<label className="collapsible-label">
Name:
<input type="text" name="Name" />
</label>
<label className="collapsible-label">
Storage Label:
<input type="text" name="StorageLabel" />
</label>
<div className="dropDownForm">
<label className="collapsible-label">
ACL:
<span className="dropdown">
<label className="dropLabel">
<select
value={selectedACL}
onChange={(e) => setValue(e.target.value)}
>
<option value="private">private</option>
<option value="public-read">public-read</option>
<option value="public-read-write">
public-read-write
</option>
<option value="authenticated-read">
authenticated-read
</option>
</select>
</label>
</span>
</label>
</div>
<div>
<input className="formSubmit" type="submit" value="Submit" />
</div>
</form>
</div>
</div>
</div>
{bucketList && <BucketTable buckets={bucketList} />}
</div>
</div>
);
};
export default Buckets;
The answer for this was to move the const columns out of the component file. I moved it into its own file and imported it to the parent component then dropped it down as a prop and everything worked fine from there. THe reasoning I am still baffled as to why that happened. I still had issues when I imported it directly to the table component but when I brought the columns in as a prop it fixed itself. But that was the fix for the infinite rendering.
const columns = [ { Header: "Bucket Name", accessor: "bucket", }, { Header: "Created By", accessor: "created_by", }, { Header: "Storage Label", accessor: "storage_label", }, { Header: "Zone", accessor: "zone", } ];
Hopefully this isn't too much of an overly basic question (though I think it might be). I've spent a few days trying to figure this out with no luck, I'm new at this.
I'm trying to use GraphQL with Shopify to generate a react-table with a bunch of information from the orders I receive on Shopify. I've gotten as far as being able to generate the data I want, but I can't seem to get it to generate a new row for each piece of data in the array.
As you can see from the image below, I just get '#1001', '#1002' in the same cell. Those are two different orders that I want on individual rows. I'm certain I need to use .map in a fashion and tell the react-table to push data, but nothing I've tried seems to work, and if it does it provides me with two individual rows- but no data in said rows.
I also think the way I'm telling the table to get the data is wrong. I need to use the accessors in my header section to define certain data that I receive from the query. Most of the accessors aren't correct barring orderNumber, Payment, customer. This is because I need to take information from lineItems and distribute it across multiple columns depending on the product. This question isn't really about this headache, but I just wanted to contextualise why some columns are missing accessors.
Here is my query:
import { gql } from '#apollo/client';
const wholesaleOrders = gql`
{
orders(first: 2, query: "tag:wholesale") {
edges {
node {
id
unpaid
fullyPaid
name
customer {
displayName
defaultAddress {
company
}
}
lineItems(first: 5) {
edges {
node {
quantity
name
}
}
}
}
}
}
}
`;
export default wholesaleOrders;
and here is my Table:
import React from 'react'
import styled from 'styled-components'
import { useTable } from 'react-table'
import { graphql, Query } from 'react-apollo';
import { gql, useQuery } from '#apollo/client';
import wholesaleOrders from './QueryData.js'
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,
});
// Render the UI for your table
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{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()} >
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
)
}
function DataTable() {
const columns = React.useMemo(
() => [
{
Header: 'Order',
columns: [
{
Header: 'Order',
accessor: 'orderNumber',
},
{
Header: 'Status',
accessor: 'payment'
},
{
Header: 'Customer',
accessor: 'customer'
},
],
},
{
Header: 'Wholesale 2kg',
columns: [
{
Header: 'EST',
accessor: 'lineItems'
},
{
Header: 'TB'
},
{
Header: 'BB'
},
{
Header: 'OTHER'
},
],
},
{
Header: 'Retail and Misc.',
columns: [
{
Header: '250g'
},
{
Header: 'Tea/Chocolate'
},
{
Header: 'Equipment'
},
{
Header: 'After Hours'
},
{
Header: 'Notes'
},
],
},
],
[]
)
return (
<Query
query={wholesaleOrders}>
{({ data, loading, error }) => {
if (loading) return <div>Loading…</div>;
if (error) return <div>{error.message}</div>;
console.log(data);
return (
<Table
data={[{orderNumber: data.orders.edges.map(({node}) => node.name )}]}
columns={columns}
defaultPageSize={10}
/>
);
}}
</Query>
)
}
export default DataTable;
and here is the result:
Result
Apologies for how verbose this is, I just wanted to cover all bases.
Thanks!
Nevermind, moron figured it out:
return (
<Query
query={wholesaleOrders}>
{({ data, loading, error }) => {
if (loading) return <div>Loading…</div>;
if (error) return <div>{error.message}</div>;
console.log(data);
return (
<Table
data={data.orders.edges.map((order) => {
return (
{
orderNumber: order.node.name,
payment: order.node.fullyPaid,
customer: order.node.customer.displayName,
});
})}
columns={columns}
defaultPageSize={10}
/>
);
}}
</Query>
)
}
Result
Days, days of trying to figure this out and I figure out after I post the question.
I´m trying to build a JSON configurable data table using the Facebook´s fixed-data-table. I´ve come to my first code as:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Icon } from 'semantic-ui-react';
import { Table, Column, Cell } from 'fixed-data-table';
const DateCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col].toLocaleString()}
</Cell>
);
const LinkCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col]}
</Cell>
);
const TextCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col]}
</Cell>
);
const NumericCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col]}
</Cell>
);
const BooleanCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col] ? <Icon name='checkmark' color='green' /> : <Icon name='remove' color='red' />}
</Cell>
);
class DataTable extends Component {
state = {
schema: this.props.schema,
data: this.props.data,
}
getCells = (schema, data) => {
let columns = [];
schema.columns.map((column, index) => {
let cell = (<TextCell></TextCell>);
let key = column.field + index;
if (column.type === 'string') {
cell = (<TextCell
data={this.state.data}
col={column.field}
/>);
}
if (column.type === 'integer') {
cell = (<NumericCell
data={this.state.data}
col={column.field}
/>);
}
if (column.type === 'boolean') {
cell = (<BooleanCell
data={this.state.data}
col={column.field}
/>);
}
let col = (<Column
header={column.title}
cell={cell}
width={100}
/>);
columns.push(col);
return;
});
return columns;
}
render() {
let schema = {
"columns": [
{
"title": "Name",
"field": "name",
"type": "string",
},
{
"title": "EIN",
"field": "ein",
"type": "string",
},
{
"title": "Active",
"field": "isactive",
"type": "boolean",
}
],
"edit": true,
"delete": true,
"sort": true
};
let data = [
{
name: 'Test1',
ein: '1234',
isactive: true
},
{
name: 'Test2',
ein: '123',
isactive: true
},
{
name: 'Test3',
ein: '12345',
isactive: true
},
];
let columns = this.getCells(schema, data);
return (
<Table
rowHeight={50}
schemaHeight={50}
maxHeight={100}
width={1000}
height={500}
rowsCount={data.length}
{...this.props}>
{columns}
</Table>
);
}
}
export default DataTable;
When running I´m getting the following error:
TypeError: data.getObjectAt is not a function
TextCell
D:\\WORKSPACE\test\src\components\shared\DataTable.js:42
39 |
40 | const TextCell = ({rowIndex, data, col, ...props}) => (
41 | <Cell {...props}>
**> 42 | {data.getObjectAt(rowIndex)[col]}**
43 | </Cell>
44 | );
45 |
I´ve tried different JSON structures with no luck. The data and schema are loaded accordingly.
This literally takes some time to wrap ones mind around initially.
I will describe it using references to code in 'fixed-data-table-2'.
The data list when used in the table is wrapped in an object that
consist of the data list and a data list filter array.
Your list data will only show up if you have wrapped it as a
DataListWrapper object along with a filter array where every
entry of the filter array is a boolean specifying if the corresponding
list row will be visible (true) or hidden (false).
See the class BuildObjectDataListStore.
There is a method 'getObjectAt' in there which when called retrieves list row columns based on the input variable name.
For example
var {my_id} = list.getObjectAt(44);
means that if the list row at index 44 contains a column named 'my_id'
then the value of that column will end up in your my_id variable.
This works only with 'underscore' as delimiter in column names,
so any lists made up of columns such as "my-id" must be converted to
"my_id" before use in 'fixed-data-table'. There exists a neat conversion table procedure in BuildObjectDataListStore for that.
Here is how you wrap a plain list array 'myList' into an object that
fixed-data-table' can render:
this.dataList = new BuildObjectDataListStore(myList);
Then this object is wrapped into an object along with a filter array:
this.filteredDataList = new DataListWrapper(this.filter, this.dataList);
And there you have it; DataListWrapper is a recognized list format that 'fixed-data-table' can render.
Nowadays people use 'fixed-data-table-2'
which enjoys continued support by Schrödinger, Inc.
The earlier 'fixed-data-table' is abandoned as a public component.
Evolving from the foundations of fixed-data-table, the power of fixed-data-table-2 is at least twofold;
Can cope with thousands of lines of list data without bogging down on
scrolling and other responsiveness.
The principle of cell renderers means any row column can be made to render anything
that HTML5 offers (text, image, video, etc).
So for some applications this list component can be spot on. A drawback can be the initial learning curve of the mentioned wrapping principles and column renderer methods.