I got a react Table that I want to delete or modify my data.
My code is below for all my cells.
deleteTableElement = (row) => {
let data = this.state.list.data;
data.splice(row.id, 1);
this.setState({
list:{
...this.state.list,
data:data
}
})
};
actionsCell = (columns) => {
columns.push({
Header: "Accion",
Cell: (props) => {
console.log(props);
return (
<div
style={{
display: "flex",
justifyContent: "space-around",
}}
>
<i
onClick={() => this.deleteTableElement(props.row)}
className="material-icons"
style={{ color: "red", cursor: "pointer" }}
>
delete
</i>
</div>
);
},
});
return columns;
};
In this case, I want to modify the item. The react table is not updating.
Whenever your component unexpectedly doesn't update, it's because you're mutating state. Literally 100% of the time:
let data = this.state.list.data;
data.splice(row.id, 1); // <- splice mutates an array, mutating state is bad
this.setState({
list:{
...this.state.list,
data:data
}
})
should be:
this.setState({
list:{
...this.state.list,
data:data.filter((d,i) => i !== row.id)
}
})
Here is a simple example of a table with deletable rows.
Pass your initial rows to the component as a prop and copy them to the state.
On click delete button, make a copy or rows without deleted row and update the state. The table will re-render.
const {useState} = React;
const MyTable = ({rows, columns}) => {
const [data, setData] = useState(rows); // Copy rows to the state
const deleteRow = index => {
// Create a copy of row data without the current row
const newData = [...data.slice(0, index), ...data.slice(index + 1)];
// Update state
setData(newData);
}
return (
<table cellSpacing="0" cellPadding="10">
<thead>
<tr>
{columns.map(column => (<th key={column}>{column}</th>))}
<th/>
</tr>
</thead>
<tbody>
{data.map((row, index) => (
<tr key={index}>
<td>{row.text}</td>
<td>{row.value}</td>
<td>
<button onClick={() => deleteRow(index)}>Delete Me !</button>
</td>
</tr>
))}
</tbody>
</table>
)
}
const tableRows = [
{
text: 'A',
value: 100,
},
{
text: 'B',
value: 200,
},
{
text: 'C',
value: 300,
},
{
text: 'D',
value: 400,
},
];
const tableColumns = ['Text', 'Count'];
ReactDOM.render(
<MyTable rows={tableRows} columns={tableColumns} />,
document.getElementById('container')
);
th, td {
border: 1px solid gray;
}
<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>
<div id="container">
</div>
thanks to #Adam and #MichaelRovinsky , theres the result and my problems are fixed with the function slice, the component state should be update with a copy
let data = this.state.list.data;
data.splice(row.id, 1);
const copy = data.slice();
this.setState({
list: {
...this.state.list,
data: copy,
},
});
Related
I would like to create an array outside useTable invocation, manipulate the array, and based on the array state make checkbox checked or unchecked. After each click on a button, length of the array is increased by adding one element to the array. When length become greater than 3, the input should be checked.
The problem is that the array state is different inside and outside checked attribute of input. Outside it works as expected: the array length increases. Inside, the array length is equal to initial length of 0.
I have attached code with some logging. I think that the relevant part probably ends with the end of useTable invocation (then is some code which I took from react-table docs with button and mock data, columns added). What changes should I introduce to the code to make it work as I expect?
import React, { useMemo, useState } from 'react';
import { useTable } from 'react-table'
function Table({ columns, data }) {
// neither stateArr nor simpleArr help reach what I would like to
const [stateArr, setStateArr] = useState([]);
let simpleArr = [...stateArr];
const handleOnButtonClick = () => {
console.log("Outside checked: simpleArr, stateArr");
console.log(simpleArr);
console.log(stateArr);
setStateArr([...stateArr, 1]);
// in this case unnecessary, since, as I understand, simpleArr is rendered and (re)assigned above
// simpleArr = [...stateArr];
};
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data,
},
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: 'checkedInputs',
Header: () => {
return (<div>
<input type="checkbox"
// working, not most elegant way to combine logging and computing boolean
checked={console.log("Inside checked: simpleArr, stateArr") || console.log(simpleArr)
|| console.log(stateArr) || simpleArr.length > 3 || stateArr.length > 3} />
</div>);
},
Cell: () => {
return (<div>R</div>);
},
},
...columns,
];
});
}
);
return (
<div>
<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>
<button type="button" onClick={handleOnButtonClick}>Click Me!</button>
</div>
)
}
function App() {
const columns = useMemo(
() => [
{
Header: 'Animal Type',
accessor: 'animalType',
},
{
Header: 'Number of legs',
accessor: 'numberOfLegs',
},
],
[],
);
const data = useMemo(
() => [
{
animalType: 'dog',
numberOfLegs: 4,
},
{
animalType: 'snake',
numberOfLegs: 0,
},
],
[],
);
return (
<Table columns={columns} data={data} />
)
}
export default App;
Stale Data
The hooks.visibleColumns.push function is called one time when the table is created. It creates a Header render component that takes some props and returns a JSX element. The function which renders the Header based on these props is called every time that the table updates. The function which creates this Header component is called once.
In your example, you create a Header component which prints out some data based on the values of simpleArr and stateArr at the time that it was created, not at the time that it was called.
Table State
If we want our Header component to render with current data then we should get that data from props. The Header is called with quite a lot of props but the one that we will use is state which is the state of the table. We will set the initialState of the table to an object { stateArr: [] }. This gets merged with the standard table state { hiddenColumns: [] }.
The table state is updated through a useReducer hook, so we update it by disptaching an action. We need a custom stateReducer to update the table state based on the contents of the action.
import React, { useMemo } from "react";
import { useTable } from "react-table";
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
dispatch,
state
} = useTable(
{
columns,
data,
initialState: {
stateArr: []
},
stateReducer: (newState, action, prevState) => {
console.log(action, newState, newState.stateArr);
switch (action.type) {
case "incrementChecks":
return {
...newState,
stateArr: [...newState.stateArr, action.payload]
};
default:
return newState;
}
}
},
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: "checkedInputs",
Header: (props) => {
console.log("header props", props); // so you can see all the data you get
console.log("stateArr", props.state.stateArr);
return (
<input
type="checkbox"
readOnly
checked={props.state.stateArr.length > 3}
/>
);
},
Cell: () => {
return <div>R</div>;
}
},
...columns
];
});
}
);
const handleOnButtonClick = () => {
// payload is the item which we are appending to the array
dispatch({ type: "incrementChecks", payload: 1 });
};
console.log("stateArr", state.stateArr);
return (
<div>
<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>
<button type="button" onClick={handleOnButtonClick}>
Append
</button>
</div>
);
}
function App() {
const columns = useMemo(
() => [
{
Header: "Animal Type",
accessor: "animalType"
},
{
Header: "Number of legs",
accessor: "numberOfLegs"
}
],
[]
);
const data = useMemo(
() => [
{
animalType: "dog",
numberOfLegs: 4
},
{
animalType: "snake",
numberOfLegs: 0
}
],
[]
);
return <Table columns={columns} data={data} />;
}
export default App;
CodeSandbox Link
I am new to react-table. I am trying to implement server-side pagination but I am not getting the logic on how to detect the page change in the new react-table version. I am using fetch data proper I am unable to detect the change. Each time I click on the Next button I should be able to change the offset value in the API endpoint in the increments of 20 to fetch new data. I am unable to perform this operation. Kindly help.
import React, { useEffect, useState, useMemo } from 'react'
import { URLs } from "../../../Config/url";
import cookie from 'react-cookies';
import "./OrderManagementScreen.css"
import { useTable, usePagination, useSortBy } from 'react-table';
import styled from 'styled-components';
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid lightgray;
width: 100%;
text-align: "center" !important;
tr {
:last-child {
td {
border-bottom: 0;
text-align: "center" !important;
}
}
}
th {
padding: 3px;
box-shadow: 0px 5px 7px 2px lightgrey;
}
td {
padding: 5px;
}
th,
td {
margin: 0;
text-align: "center";
border-bottom: 1px solid #73737361;
border-right: 1px solid #73737361;
:last-child {
border-right: 0;
}
}
}
.pagination {
}
`;
const WrapperTable = styled.div`
background: #ffffff;
box-shadow: 3px 3px 2px 0px rgb(162 161 161 / 75%) !important;
border-radius: 5px;
`
const Table = ({ columns, data }) => {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
page, // Instead of using 'rows', we'll use page,
// which has only the rows for the active page
// The rest of these things are super handy, too ;)
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize, sortBy },
} = useTable(
{
columns,
data,
initialState: { pageIndex: 0 },
},
useSortBy,
usePagination
);
// const sorted = column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : "";
// const sorted = column.isSorted ? (column.isSortedDesc ? {borderTop:"1px solid "} :{borderTop:"1px solid "}) : "";
// Render the UI for your table
return (
<>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render("Header")}
{/* Add a sort direction indicator */}
<span>
{column.isSorted
? column.isSortedDesc
? " 🔽"
: " 🔼"
: ""}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
{/*
Pagination can be built however you'd like.
This is just a very basic UI implementation:
*/}
<div className="pagination">
{/* <button
className="pagination-btn"
onClick={() => gotoPage(0)}
disabled={!canPreviousPage}
>
First
</button> */}
<button
className="pagination-btn"
onClick={() => previousPage()}
disabled={!canPreviousPage}
>
Previous
</button>
<span className="pagination-btn text-center">
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<button
className="pagination-btn"
onClick={() => nextPage()}
disabled={!canNextPage}
>
Next
</button>
{/* <button
className="pagination-btn"
onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage}
>
Last
</button> */}
{/* <span>
| Go to page:{' '}
<input
type="number"
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px' }}
/>
</span> */}
{/* <select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select> */}
</div>
</>
);
};
const OrderManagementScreen = () => {
const token = cookie.load("userObj").data.token;
//orderid, outletname, area, distributor, ordervalue, outlet type, discount group, salesofficer,order
const [tableData, SetData] = useState([]);
const [loading, setLoading] = React.useState(false);
const fetchIdRef = React.useRef(0);
const sortIdRef = React.useRef(0);
const columns = React.useMemo(
() => [
{
Header: "Order Id",
accessor: "id",
},
{
Header: "Outlet Name",
id: "outlet_detail",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.name);
});
return output.join(', ');
}
},
{
Header: "Area",
id: "area",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.area__name);
});
return output.join(', ');
}
},
{
Header: "Distributor",
id: "distributor",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.distributor_name);
});
return output.join(', ');
}
},
{
Header: "Order Value",
accessor: "total_price",
},
{
Header: "Outlet Type",
id: "outlet_type__name",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.final_value);
});
return output.join(', ');
}
},
{
Header: "Discount Group",
id: "discount__name",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.discount__name);
});
return output.join(', ');
}
},
{
Header: "Sales Officer",
id: "sales_officer",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.by_user__username);
});
return output.join(', ');
}
}
],
[]
);
const listdata = async () => {
const response = await fetch(`${URLs.orderUrl}?limit=20&offset=0`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Token ${token}`
}
})
const data = await response.json();
SetData(data);
}
const fetchData = React.useCallback(({ pageSize, pageIndex, sortBy }) => {
// This will get called when the table needs new data
// You could fetch your data from literally anywhere,
// even a server. But for this example, we'll just fake it.
// Give this fetch an ID
console.log(pageIndex);
console.log(pageSize);
const fetchId = ++fetchIdRef.current;
// Set the loading state
setLoading(true);
// We'll even set a delay to simulate a server here
setTimeout(() => {
// Only update the data if this is the latest fetch
if (fetchId === fetchIdRef.current) {
const startRow = pageSize * pageIndex;
const endRow = startRow + pageSize;
if (sortBy.length === 0) {
SetData(tableData.sort().slice(startRow, endRow));
} else {
SetData(
tableData
.sort((a, b) => {
const field = sortBy[0].id;
const desc = sortBy[0].desc;
if (a[field] < b[field]) {
return desc ? -1 : 1;
}
if (a[field] > b[field]) {
return desc ? 1 : -1;
}
return 0;
})
.slice(startRow, endRow)
);
}
// Your server could send back total page count.
// For now we'll just fake it, too
// setPageCount(Math.ceil(serverData.length / pageSize));
setLoading(false);
}
}, 1000);
}, []);
useEffect(() => {
listdata();
}, [])
return (
<div className="p-3 text-center">
<h4>Order Management</h4>
<WrapperTable>
<Styles>
<Table columns={columns} fetchData={fetchData} data={tableData} />
</Styles>
</WrapperTable>
</div>
)
}
export default OrderManagementScreen;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
First, you need to understand the basic difference between client-side pagination and server-side pagination.
In client-side pagination, we already have all the data for all the pages which we need to display in the table that means we know the total count as well (totalcount=pagesize*number of pages).
Now compare this with server-side pagination. We shall be getting the slice of data which we request means if we are keeping page size as 10 and we have 100 data at our server but since we requested 10 so we'll only get 10 items. Then how will the pagination component know what will be the total number of pages which he needs to display?
That's why we need a total count from the server as well when we are fetching the data.
But wait, do we need it every time? Well, it depends on your use-case. In general, we need the total count either for the first time or in case we are doing any find or filter.
Now coming to your solution-
In react-table if we did not explicitly set the flag manualPagination as true then it will process the number of pages based of your supplied data and the pagesize so it will auto handle the pagination. So we need to make this manualPagination as true in options we passed to useTable and also we need to supply the total number of pages that is pageCount. So this will be something like
useTable(
{
columns,
data,
initialState: { pageIndex: 0 },
},
useSortBy,
usePagination,
manualPagination: true,
pageCount: (totalcount/pagesize),//you can handle this calculation in your fetchdata method
);
and then add your fetch data call inside a new useEffect with your pageindex and the
pagesize as dependencies
React.useEffect(() => {
fetchData({ pageIndex, pageSize })
}, [fetchData, pageIndex, pageSize])
I hope this will solve your issue. This is also well explained in the react-table documentation with proper codeshare example. Check here
My scenario is that I have a table that is generated based on data. In one of the columns I would like to pass in a 'remove' button/component, which is meant to remove the row that it is in.
My issue is that the 'remove' button component needs to be given the row so it can determine which data to remove.
If you look in Table.js, you can see where I render the prop as a component '{col.component}' - But how can I also pass values to the components action?Examples below.
App.js
import React, { Component } from 'react';
import Table from './Table';
import Table from './RemoveButton';
class App extends Component {
//Data is the array of objects to be placed into the table
let data = [
{
name: 'Sabrina',
age: '6',
sex: 'Female',
breed: 'Staffordshire'
},
{
name: 'Max',
age: '2',
sex: 'Male',
breed: 'Boxer'
}
]
removeRow = name => {
//Remove object from data that contains name
}
render() {
//Columns defines table headings and properties to be placed into the body
let columns = [
{
heading: 'Name',
property: 'name'
},
{
heading: 'Age',
property: 'age'
},
{
heading: 'Sex',
property: 'sex'
},
{
heading: 'Breed',
property: 'breed'
},
{
heading: '',
component: <RemoveButton action=removeRow()/>
}
]
return (
<>
<Table
columns={columns}
data={data}
propertyAsKey='name' //The data property to be used as a unique key
/>
</>
);
}
}
export default App;
RemoveButton.js
import React from 'react';
const RemoveButton = action => {
return(
<button onClick={action}>Remove Row</button>
)
}
export default RemoveButton;
Table.js
const Table = ({ columns, data, propertyAsKey }) =>
<table className='table'>
<thead>
<tr>{columns.map(col => <th key={`header-${col.heading}`}>{col.heading}</th>)}</tr>
</thead>
<tbody>
{data.map(item =>
<tr key={`${item[propertyAsKey]}-row`}>
{columns.map(col => {
if(col.component){
return(<td> key={`remove-${col.property}`}>{col.component}</td>)
} else {
return(<td key={`${item[propertyAsKey]}-${col.property}`}>{item[col.property]}</td>)
}
})}
</tr>
)}
</tbody>
</table>
Instead of passing down a component in the column, you could pass down the removeRow function to the Table component as a regular prop, and have another value on the remove column to indicate when you should render the remove button for that column, and pass the item name when you invoke it.
class App extends React.Component {
state = {
data: [
{
name: "Sabrina",
age: "6",
sex: "Female",
breed: "Staffordshire"
},
{
name: "Max",
age: "2",
sex: "Male",
breed: "Boxer"
}
]
};
removeRow = name => {
this.setState(({ data }) => ({
data: data.filter(el => el.name !== name)
}));
};
render() {
let columns = [
{
heading: "Name",
property: "name"
},
{
heading: "Age",
property: "age"
},
{
heading: "Sex",
property: "sex"
},
{
heading: "Breed",
property: "breed"
},
{
heading: "",
removeCol: true
}
];
return (
<Table
columns={columns}
data={this.state.data}
removeRow={this.removeRow}
propertyAsKey="name"
/>
);
}
}
const Table = ({ columns, data, removeRow, propertyAsKey }) => (
<table className="table">
<thead>
<tr>
{columns.map(col => (
<th key={`header-${col.heading}`}>{col.heading}</th>
))}
</tr>
</thead>
<tbody>
{data.map(item => (
<tr key={`${item[propertyAsKey]}-row`}>
{columns.map(col => {
if (col.removeCol) {
return (
<td key={`remove-${col.property}`}>
<button onClick={() => removeRow(item.name)}>
Remove Row
</button>
</td>
);
} else {
return (
<td key={`${item[propertyAsKey]}-${col.property}`}>
{item[col.property]}
</td>
);
}
})}
</tr>
))}
</tbody>
</table>
);
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I am trying to avoid using react state for better performance. However, i have no idea on how can i sort table data. I tried to have sort function in the column that needs sorting with the type("asc" or "desc") and based on name or title or like that. This is the configuration and code for my table
code
const sortBy = (data, key, type = "asc") => data.sort((a, b) => a[key].toLowerCase() < b[key].toLowerCase());
const columns = {
name: {
key: "name",
label: "Listing Name",
css: "color: #444; font-size: 1.1rem; font-weight: bold;",
content: (item: Object) => (
<NameColumn>
{item.url && <Avatar name={item.name} url={item.url} size={80} type="square" />}
<NameWrapper>
<Name>{item.name}</Name>
{item.location && <SubName color='#797979'>{item.location}</SubName>}
</NameWrapper>
</NameColumn>
)
},
agent: {
key: "agent",
label: "Agent",
sort: (data: Array<Object>, item: Object) => sortBy(data, item, 'desc'),
isSortable: true,
hideOnPhone: true
},
price: {
key: "price",
label: "Prices",
hideOnPhone: true
},
};
const userData = [
{
id: 1,
name: "Rahul",
location: 'Delhi',
agent: "hello man",
price: '$15000',
},
{
id: 2,
name: "Sachin Tendulkar",
location: 'Delhi',
agent: "Mumbai Inc",
price: '$15000',
},
];
const rowConfig = {
uniqueKey: "id",
css: `
height: 100px;
&:hover {
background-color: rgba(216, 216, 216, 0.2)};
}
`,
onClick: (e, item) => {
console.log("row was clicked", item);
}
};
type Props = {
location: Object
};
const Tables = ({ location }: Props) => {
const queries = new URLSearchParams(location.search);
return (
<Main>
<Table
rowConfig={rowConfig}
columns={columns}
data={userData}
totalPages={10}
currentPage={
queries.has("page") ? parseInt(queries.get("page"), 10) : 1
}
basePageLink={""}
/>
</Main>
);
};
export default Tables;
const Table = ({
columns,
data = [],
rowConfig: { uniqueKey = "id", css , onClick } = {},
currentPage,
totalPages,
basePageLink
}: Props) => {
const headerColumns = () =>
Object.keys(columns).map(key => (
<Th
key={key}
align={columns[key].align}
width={columns[key].width}
onClick={() => columns[key].isSortable && columns[key].sort(data, key)}
css={columns[key].cssHeader}
>
{columns[key].label ? columns[key].label : ""}
</Th>
));
const cell = (key, item) => (
<Td
key={key}
align={columns[key].align}
width={columns[key].width}
css={columns[key].css}
>
{columns[key].content ? columns[key].content(item) : item[key]}
</Td>
);
const row = (item: Object) => (
<Tr
key={item[uniqueKey]}
css={css}
onClick={onClick ? (e: Event) => onClick(e, item) : null}
>
{Object.keys(columns).map(key => cell(key, item))}
</Tr>
);
return (
<Main>
<T>
<thead>
<tr>{headerColumns()}</tr>
</thead>
<tbody>{data.map(i => row(i))}</tbody>
</T>
<TablePagination
currentPage={currentPage}
totalPages={totalPages}
basePageLink={basePageLink}
/>
</Main>
);
};
export default Table;
The way i am doing wont work cause i am not using the react state which will notify react that the state has changed so re-render the agent column(for now sorting is used in agent column only so).
Any suggesstions? Help would be appreciated!
The goal of my small React experiment is "clear the initial value of this.state.numString (outputs an empty string), then concatenate the clicked numbers into this.state.numString". To make it execute asynchronously, I took advantage of this.setState's callback where the concatenation of number strings happen.
class App extends Component {
state = {
numString: '12'
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
this.setState({
numString: ''
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}
The result was not what I expected; it only adds the current number I click into the empty string then change it into the one I click next, no concatenation of string numbers happens.
Here is one way of doing this. As I said in my comment you are resetting the string in every setState. So, you need some kind of condition to do that.
class App extends React.Component {
state = {
numString: '12',
resetted: false,
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if ( !this.state.resetted ) {
this.setState({
numString: '',
resetted: true,
}, () => {
this.setState( prevState => ({
numString: prevState.numString.concat(num)
}))
})
} else {
this.setState(prevState => ({
numString: prevState.numString.concat(num)
}))
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = { padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem' };
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can do something like below to clear the state immediately and concatenate the state with previous state value
this.setState({
numString: ''
}, () => {
this.setState( prevState => ({
numString: prevState.numString + num
}));
});
The code above in your question , in first setState you are setting variable to empty and in the second setState it is concatenating new value with empty string state. Thats why it is not working.
Try something like below:
class App extends Component {
state = {
numString: '12',
isFirstTime: true
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if(this.state.isFirstTime){
this.setState({
numString: '',
isFirstTime: false
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}else{
this.setState({
numString: this.state.numString.concat(num)
})
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data- num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}