I am using react-table to display the data. I want to keep my table column outside the react component since it is too big to put in there and reusability purpose as well. I made a table config to hold all my table config. The problem is I am using some of my local state reference in columns. So when I import column from config file it gives error where I have used this.state.
tableconfig.js
export const table = [
{
resizable: false,
width: 50,
Cell: ({ original }) => {
const { pageActivity, pageNumber } = this.state;
const currentPageActivityRow = {
...get(pageActivity[pageNumber], "selectedRows")
};
return (
<Checkbox
checked={currentPageActivityRow[original.id] === true}
onChange={() => this.selectRows(original.id)}
/>
);
}
},
{
Header: "Contact ID",
accessor: "id"
}
];
tableComponent.js
import {table} from '../config';
constructor(props){
super(props);
this.state = {
pageNumber: 1,
search: "",
error: "",
dialogOpen: false,
isFilerActive: false,
selectedFilters: {},
pageActivity: {}
};
this.columns = table
}
maybe try binding your table? It has to be a function though
const table = () => (
[
{
resizable: false,
width: 50,
Cell: ({ original }) => {
const { pageActivity, pageNumber } = this.state;
const currentPageActivityRow = {
...get(pageActivity[pageNumber], "selectedRows")
};
return (
<Checkbox
checked={currentPageActivityRow[original.id] === true}
onChange={() => this.selectRows(original.id)}
/>
);
}
},
{
Header: "Contact ID",
accessor: "id"
}
];
)
// tableComponent.js
constructor(props){
super(props);
this.state = {
pageNumber: 1,
search: "",
error: "",
dialogOpen: false,
isFilerActive: false,
selectedFilters: {},
pageActivity: {}
};
this.columns = table.bind(this);
}
Related
This has been asked quite a few times, so sorry, but I can't work this out. I hae read the docs, but I couldn't find anything that worked, so I obvioulsy don't understand what's happening here.
class DivisionExtraDetails extends Component {
constructor(props) {
super(props);
this.state = {
error: false,
loading: true,
removing: null,
saving: false,
geofence: false,
startTimeOfDay: ''
};
}
componentDidMount() {
const { division } = this.props;
Axios.get(`${API_URL}assetgroups/${division.id}`)
.then(r => {
this.setState({
loading: false,
geofence: r.data.geofence_assign,
startTimeOfDay: r.data.day_start
});
})
.catch(err => {
if (!Axios.isCancel(err)) {
this.setState({
error: true
});
}
});
}
render() {
const { error, loading, geofence, saving, startTimeOfDay } = this.state;
const { assignText, division } = this.props;
const geoFenceOptions = [
{value: 1, label: 'YES'},
{value: 0, label: 'NO'},
{value: null, label: 'Not yet set'},
];
return (
<React.Fragment>
<div className="col-5">
<span>Assign a GeoFence (Yes/No)</span>
<Select
selectedValue={geofence}
options={geoFenceOptions}
className="basic-multi-select"
classNamePrefix="select"
onChange={this.handleChange}
/>
</div>
</React.Fragment>
);
}
}
I've also tried:
defaultValue={geofence}
selectedValue={geofence}
value={geofence}
And I've also tried the variable as:
{this.state.geofence}
I can see the call to the db is correctly populating the state if I view it in dev tools.
But I can't work it out. If anyone can help with this seemingly simple task, that would be great. Thanks.
You are passing value as boolean or string in react select but you are passing objects as it's options so that's why react select was not able to find show default value.
To Solve this you need to pass correct object in value prop so try something like below:-
class DivisionExtraDetails extends Component {
constructor(props) {
super(props);
this.state = {
error: false,
loading: true,
removing: null,
saving: false,
geofence: false,
startTimeOfDay: '',
// set geoFenceOptions as state so we can use it later
geoFenceOptions: [
{value: true, label: 'YES'},
{value: false, label: 'NO'},
{value: null, label: 'Not yet set'},
];
};
}
// find correct geoFenseOption based on provided value
getGeoFenceValue = (value) => {
return this.state.geoFenceOptions.find(option => option.value === value);
}
componentDidMount() {
const { division } = this.props;
Axios.get(`${API_URL}assetgroups/${division.id}`)
.then(r => {
this.setState({
loading: false,
geofence: this.getGeoFenceValue(r.data.geofence_assign), // call function to find correct option
startTimeOfDay: r.data.day_start
});
})
.catch(err => {
if (!Axios.isCancel(err)) {
this.setState({
error: true
});
}
});
}
render() {
const { error, loading, geofence, saving, startTimeOfDay, geoFenceOptions } = this.state;
const { assignText, division } = this.props;
return (
<React.Fragment>
<div className="col-5">
<span>Assign a GeoFence (Yes/No)</span>
<Select
selectedValue={geofence}
options={geoFenceOptions}
className="basic-multi-select"
classNamePrefix="select"
onChange={this.handleChange}
/>
</div>
</React.Fragment>
);
}
constructor(props) {
super(props);
this.state = {
// ... code omitted
geofence: /* set default value here */
};
}
I try to convert the class component in my react app below :
import React, { Component } from 'react'
import ReactTable from 'react-table'
import api from '../api'
import styled from 'styled-components'
import 'react-table/react-table.css'
const Wrapper = styled.div`
padding: 0 40px 40px 40px;
`
const Update = styled.div`
color: #ef9b0f;
cursor: pointer;
`
const Delete = styled.div`
color: #ff0000;
cursor: pointer;
`
class UpdateVoter extends Component {
updateUser = event => {
event.preventDefault()
window.location.href = `/voters/update/${this.props.id}`
}
render() {
return <Update onClick={this.updateUser}>Update</Update>
}
}
class DeleteVoter extends Component {
deleteUser = event => {
event.preventDefault()
if (
window.confirm(
`Do you want to delete this voter ${this.props.id} permanently?`,
)
) {
api.deleteVoterById(this.props.id)
window.location.reload()
}
}
render() {
return <Delete onClick={this.deleteUser}>Delete</Delete>
}
}
class VotersList extends Component {
constructor(props) {
super(props)
this.state = {
voters: [],
columns: [],
isLoading: false,
}
}
componentDidMount = async () => {
this.setState({ isLoading: true })
await api.getAllVoters().then(voters => {
this.setState({
voters: voters.data.data,
isLoading: false,
})
})
}
render() {
const { voters, isLoading } = this.state
const columns = [
{
Header: 'ID',
accessor: '_id',
filterable: true,
},
{
Header: 'No KK',
accessor: 'nkk',
filterable: true,
},
{
Header: 'NIK',
accessor: 'nik',
filterable: true,
},
{
Header: 'Nama',
accessor: 'nama',
filterable: true,
},
{
Header: 'Alamat',
accessor: 'alamat',
filterable: true,
},
{
Header: '',
accessor: '',
Cell: function(props) {
return (
<span>
<DeleteVoter id={props.original._id} />
</span>
)
},
},
{
Header: '',
accessor: '',
Cell: function(props) {
return (
<span>
<UpdateVoter id={props.original._id} />
</span>
)
},
},
]
let showTable = true
if (!voters.length) {
showTable = false
}
return (
<Wrapper>
{showTable && (
<ReactTable
data={voters}
columns={columns}
loading={isLoading}
defaultPageSize={10}
showPageSizeOptions={true}
minRows={0}
/>
)}
</Wrapper>
)
}
}
export default VotersList
to the functional component with hooks, like this :
import React, {useState, useEffect} from 'react'
import ReactTable from 'react-table'
import api from '../api'
import styled from 'styled-components'
import 'react-table/react-table.css'
const Wrapper = styled.div`
padding: 0 40px 40px 40px;
`
const Update = styled.div`
color: #ef9b0f;
cursor: pointer;
`
const Delete = styled.div`
color: #ff0000;
cursor: pointer;
`
function UpdateVoter(props) {
const updateUser = event => {
event.preventDefault()
window.location.href = `/voters/update/${props.id}`
}
return <Update onClick={updateUser}>Update</Update>
}
function DeleteVoter(props) {
const deleteUser = event => {
event.preventDefault()
if (
window.confirm(
`Do tou want to delete this voter ${props.id} permanently?`,
)
) {
api.deleteVoterById(props.id)
window.location.reload()
}
}
return <Delete onClick={deleteUser}>Delete</Delete>
}
function VotersListSpecific(props) {
const [state, setState] = useState ({
voters: [],
columns: [],
isLoading: false,
})
useEffect(() => {
async function fetchData() {
setState({ ...state, isLoading: true })
let voters = await api.getAllVoters()
setState({
voters: voters.data.data,
...state,
isLoading: true,
})
}
fetchData()
console.log(fetc)
}, [])
const { voters, isLoading } = state
const columns = [
{
Header: 'ID',
accessor: '_id',
},
{
Header: 'No KK',
accessor: 'nkk',
},
{
Header: 'NIK',
accessor: 'nik',
},
{
Header: 'Nama',
accessor: 'nama',
},
{
Header: 'Alamat',
accessor: 'alamat',
},
{
Header: '',
accessor: '',
Cell: function(props) {
return (
<span>
<DeleteVoter id={props.original._id} />
</span>
)
},
},
{
Header: '',
accessor: '',
Cell: function(props) {
return (
<span>
<UpdateVoter id={props.original._id} />
</span>
)
},
},
]
let showTable = true
if (!voters.length) {
showTable = false
}
return (
<Wrapper>
{showTable && (
<ReactTable
data={voters}
columns={columns}
loading={isLoading}
defaultPageSize={10}
showPageSizeOptions={true}
minRows={0}
/>
)}
</Wrapper>
)
}
export default VotersList
But, the code is not working. The table is not displayed. The "voters" array inside the state is empty. Besides that, I also got this warning :
React Hook useEffect has a missing dependency: 'state'. Either include
it or remove the dependency array. You can also do a functional update
'setState(s => ...)' if you only need 'state' in the 'setState' call
react-hooks/exhaustive-deps
I really need help to solve this. Thank you in advance.
My Suggestion
First of all, useState is not like the this.state in class components. You are recommended to assign each primitive state with a useState function. For example, in you VotersListSpecific component, instead of having one state wrapping up voters, columns and isLoading, you could have:
const [voters, setVoters] = useState([])
const [columns, setColumns] = useState([])
const [isLoading, setLoading] = useState(false)
Still it can be further optimized using useReducer, but it would be way too off-topic for now. You can checkout the official docs if interested.
Your Problem
Now let us analyze what you did right and what you did wrong.
The Rights
When you change the state, you use the syntax setState({ ...state, isLoading: true }), which is correct as React only toggles the re-render when the reference of the object is changed. If you use something like this:
state.isLoading = false
setState(state)
The reference if the state is not changed in this case, so React will not re-render.
The Wrongs
When you call const { voters, isLoading } = state, the voters variable points to the voters field of the state, which is an empty array at the time of first render. Some time later, when the new state is created with the new voters, the new state.voters actually points to a new array. But React does not know about this change as you have explicitly pointed the voter variable to the original empty array, which is a field of the old state.
A fix to this would be something like:
const [voters, setVoters] = useState([])
useEffect(async () => {
setVoters(await api.getAllVoters())
}, [])
return (
<ReactTable data={voters}/>
)
Another approach can be using data={state.voters}.
Welcome to React Functional programming.
I don't have all your dependencies so I'm trying to answer your question by eye instead of actual compiling.
A grain of salt when updating state is, instead of doing this:
setState({
...state,
...yourstuffhere
})
do this:
setState(prevState => {
return {
...prevState,
...yourstuffhere
}
});
What happens is by doing above, you are making sure that you have updated data from previous state. Remember that setState is an async function. Unless you do it like that, you might accidentally lose some data on the fly.
useEffect(() => {
async function fetchData() {
setState(prevState => {
return {
...prevState, isLoading: true
}
})
let voters = await api.getAllVoters()
setState(prevState => {
return {
voters: voters.data.data,
...prevState,
isLoading: true,
}
})
}
fetchData()
console.log(fetc)
}, [])
I have this scenario where I have a table(react-table), where I am applying column level filtering. I have extracted this as a separate component(DropdDown Component) and this can be attached to any column . I am maintaining a method inside parent component which picks up the union of all the values i.e., selected values of all the dropdowns and then apply server side filtering.
Now the challenge here is , How can i get this consolidated values inside the parent component method?
This DropDown component has list of unique values with respect to that column, there is an Apply button , which applies the server side filtering. Now if I jump onto another column, I need to get the previously checked values and also the current values.
Inside handleSetData() filtering logic is written, I need to get the data from DropDown Component. Everytime I click on Apply on a column filter, I need to get the previously checked values as well.
Can someone help me with this:
Code Sandbox: https://codesandbox.io/s/quizzical-glitter-np8iw
App Component
import * as React from "react";
import { render } from "react-dom";
import ReactTable from "react-table";
import "./styles.css";
import "react-table/react-table.css";
import DropDownComponent from "./DropDown";
interface IState {
data: {}[];
columns: {}[];
}
interface IProps {}
export default class App extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
data: [
{ firstName: "aaaaa", status: "Pending", visits: 155 },
{ firstName: "aabFaa", status: "Pending", visits: 155 },
{ firstName: "adaAAaaa", status: "Approved", visits: 1785 },
{ firstName: "aAaaaa", status: "Approved", visits: 175 },
{ firstName: "adaSaaa", status: "Cancelled", visits: 165 },
{ firstName: "aaaaa", status: "Cancelled", visits: 157 },
{ firstName: "aaaaa", status: "Approved", visits: 153 },
{ firstName: "aaaaa", status: "Pending", visits: 155 }
],
columns: []
};
}
handleSetState = (columns: any) => {
this.setState({ columns });
};
handleSetData = (value: any) => {
console.log(value); // Here filtering logic is written, I need to get the data from DropDown Component. Everytime I click on Apply on a column filter, I need to get the previously checked values as well
};
componentDidMount() {
let columns = [
{
Header: () => (
<div>
<div style={{ position: "absolute", marginLeft: "10px" }}>
<DropDownComponent
data={this.state.data}
handleSetData={this.handleSetData}
param="firstName"
/>
</div>
<span>First Name</span>
</div>
),
accessor: "firstName",
sortable: false,
show: true,
displayValue: " First Name"
},
{
Header: () => (
<div>
<div style={{ position: "absolute", marginLeft: "10px" }}>
<DropDownComponent
data={this.state.data}
handleSetData={this.handleSetData}
param="status"
/>
</div>
<span>Status</span>
</div>
),
accessor: "status",
sortable: false,
show: true,
displayValue: " Status "
},
{
Header: "Visits",
accessor: "visits",
sortable: false,
show: true,
displayValue: " Visits "
}
];
this.setState({ columns });
}
render() {
const { data, columns } = this.state;
return (
<div>
<ReactTable
data={data}
columns={columns}
defaultPageSize={10}
className="-striped -highlight"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
DropDown Component
import * as React from "react";
import { Button, Checkbox, Icon } from "semantic-ui-react";
interface IProps {
data: {}[];
handleSetData(arr: any): void;
param: string;
}
interface IState {
showList: boolean;
optionsArr: {}[];
originalState: {}[];
}
export default class DropDownComponent extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
showList: false,
optionsArr: [],
originalState: []
};
}
toggleList = () => {
this.setState(prevState => ({ showList: !prevState.showList }));
};
handleItemClick = (event: React.FormEvent<HTMLInputElement>, data: any) => {
const index = this.state.optionsArr.findIndex(
(item: any) => item.text === data.name
);
const optionsArr = this.state.optionsArr.map((prevState: any, i: any) =>
i === index
? {
key: prevState.key,
text: prevState.text,
checked: !prevState.checked
}
: prevState
);
this.setState({ optionsArr });
};
submitSelection = () => {
console.log(this.state.optionsArr.filter((item: any) => item.checked)); // This gives me selecte ones
let checkedValues: any = this.state.optionsArr.filter(
(item: any) => item.checked
);
this.setState({ originalState: this.state.optionsArr }, () =>
this.props.handleSetData(checkedValues)
);
};
componentDidMount() {
if (this.props.data) {
let arr = this.props.data;
let uniqueValues = Array.from(
new Set(arr.map((arr: any) => arr[this.props.param]))
);
var optionsArr = [];
for (let i = 0; i < uniqueValues.length; i++) {
var options: any = {};
options["key"] = uniqueValues[i];
options["text"] = uniqueValues[i];
options["checked"] = false;
optionsArr.push(options);
}
this.setState({ optionsArr: optionsArr, originalState: optionsArr });
}
}
clearSelection = (event: any) => {
// Push it to previous state, before cancel was clicked
this.setState({ showList: false, optionsArr: this.state.originalState });
};
render() {
let { showList } = this.state;
let visibleFlag: string;
if (showList === true) visibleFlag = "visible";
else visibleFlag = "";
return (
<div>
<div style={{ position: "absolute" }}>
<div
className={
"ui scrolling dropdown column-settings customized " +
visibleFlag +
" " +
this.props.menuDirection
}
>
<Icon className="filter" onClick={this.toggleList} />
{this.state.optionsArr.length > 0 ? (
<>
<div className="menu-item-holder">
{this.state.optionsArr.map((item: any, i: number) => (
<div className="menu-item" key={i}>
<Checkbox
name={item.text}
onChange={this.handleItemClick}
checked={item.checked}
label={item.text}
/>
</div>
))}
</div>
<div className="menu-btn-holder">
<Button size="small" onClick={this.submitSelection}>
Apply
</Button>
<Button size="small" onClick={this.clearSelection}>
Cancel
</Button>
</div>
</>
) : (
""
)}
</div>
</div>
</div>
</div>
);
}
}
I have modified your DropDown component. I make it receive a list of items to generate the checkboxes. Instead of sending the whole data object to the DropDown component, I think it makes more sense send a ready list to them, the Main component should generate the right data structure (I haven't done that, you have to create those functions). In the component, I create three states to manage the component.
Obs: I removed typescript to make faster for me.
condesandbox
I am trying to follow this page sample:
https://ant.design/components/table/
The antd filtering sample to be precise
I have a column which I know its 2 possible values only.
I can see in the debugger that the handlechange event is executed, but after click OK in the filter, the table is not filtered as it should
My best guess I am missing something on the OnFilter event
import React, { Component } from 'react';
import { Table, Tag, Button} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListPageTemplatesWithSelection extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
filteredInfo: null,
sortedInfo: null,
};
this.handleChange= this.handleChange.bind(this);
this.clearFilters= this.clearFilters.bind(this);
this.clearAll= this.clearAll.bind(this);
}
handleChange(pagination, filters, sorter){
console.log('Various parameters', pagination, filters, sorter);
this.setState({
filteredInfo: filters,
sortedInfo: sorter,
});
}
clearFilters(){
this.setState({ filteredInfo: null });
}
clearAll(){
this.setState({
filteredInfo: null,
sortedInfo: null,
});
}
fetchData = () => {
adalApiFetch(fetch, "/PageTemplates", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.Id,
Name: row.Name,
SiteType: row.SiteType,
Tags: row.Tags
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render(){
let { sortedInfo, filteredInfo } = this.state;
sortedInfo = sortedInfo || {};
filteredInfo = filteredInfo || {};
const columns = [
{
title: 'Id',
dataIndex: 'key',
key: 'key',
},
{
title: 'Name',
dataIndex: 'Name',
key: 'Name',
},
{
title: 'Site Type',
dataIndex: 'SiteType',
key: 'SiteType',
filters: [
{ text: 'Modern Team Site', value: 'Modern Team Site' },
{ text: 'CommunicationSite', value: 'CommunicationSite' },
],
filteredValue: filteredInfo.name || null,
onFilter: (value, record) => record.Tags.includes(value),
},{
title: 'Tags',
key: 'Tags',
dataIndex: 'Tags',
render: Tags => (
<span>
{Tags && Tags.map(tag => {
let color = tag.length > 5 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return <Tag color={color} key={tag}>{tag.toUpperCase()}</Tag>;
})}
</span>
),
}
];
const rowSelection = {
selectedRowKeys: this.props.selectedRows,
onChange: (selectedRowKeys) => {
this.props.onRowSelect(selectedRowKeys);
}
};
return (
<div>
<Button onClick={this.clearFilters}>Clear filters</Button>
<Button onClick={this.clearAll}>Clear filters and sorters</Button>
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} onChange={this.handleChange} />
</div>
);
}
}
export default ListPageTemplatesWithSelection;
In your SiteType column, you have mistakenly set filteredValue prop to filteredInfo.name. But the filter is not on a name column, it is on SiteType column.
Change this line from:
filteredValue: filteredInfo.name || null,
To:
filteredValue: filteredInfo.SiteType || null,
And it should be fine.
You need execute the fetchData function in the handleChange function. Add filter params to ajax request. Just like this:
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
}
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;