I am building an app in Ag-grid React
I would like the grid to highlight a row if the user has tagged it by clicking on a checkbox. I am using rowClassRules, and it works fine: if the user edits the value of the tag field for a row from false to true, the row becomes highlighted
When I add in a cell renderer to make the tag field a checkbox it stops working, see code below
Any advice on what I am doing wrong would be appreciated
index.js
import React, { useState } from "react";
import { render } from "react-dom";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
import "./index.css"
const App = () => {
const AgGridCheckbox = (props) => {
const boolValue = props.value && props.value.toString() === "true";
const [isChecked, setIsChecked] = useState(boolValue);
const onChanged = () => {
props.setValue(!isChecked);
setIsChecked(!isChecked);
};
return (
<div>
<input
type="checkbox"
checked={isChecked}
onChange={onChanged}
/>
</div>
);
};
const [rowData] = useState([
{ tag: true, make: "Toyota", model: "Celica", price: 35000 },
{ tag: false, make: "Ford", model: "Mondeo", price: 32000 },
{ tag: false, make: "Porsche", model: "Boxter", price: 72000 },
]);
const [columnDefs] = useState([
{ field: "tag", cellRenderer: AgGridCheckbox },
// { field: "tag", editable: true },
{ field: "make" },
{ field: "model" },
{ field: "price" },
]);
const gridOptions = {
rowClassRules: {
"row-tagged": (params) => params.api.getValue("tag", params.node),
},
};
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 800 }}>
<AgGridReact
gridOptions={gridOptions}
rowData={rowData}
columnDefs={columnDefs}
></AgGridReact>
</div>
);
};
render(<App />, document.getElementById("root"));
index.css
.row-tagged {
background-color: #91bd80 !important;
}
I've done some more research and if I add redrawRows() to the onChanged() handler in the cell renderer thus:
const onChanged = () => {
props.setValue(!isChecked);
setIsChecked(!isChecked);
setRowData(rowData);
console.log(props);
props.api.redrawRows({ rowNodes: [props.node] });
};
It now works. Note that passing { rowNodes: [props.node] } means (I assume) that it only redraws a single row.
Supplementary Question: Is this the right way to go? Is there a more efficient way?
Related
I'm using ag-grid-enterprise v28.1.3 in a next.js app and trying to utilize server-side row filling. Since I've introduced rowModelType="serverSide", horizontal scrolling seems to have disappeared. No scroll bar, no nothing.
Here is the setup for my grid:
<div
className="ag-theme-custom"
style={{
height: "100%",
width: "97%",
marginTop: "5rem",
marginLeft: "1.5%",
overflow: "scroll",
}}
>
<AgGridReact
columnDefs={columnDefs}
defaultColDef={defaultColDef}
animateRows={true}
rowSelection="multiple"
rowModelType="serverSide"
pagination={true}
paginationPageSize={50}
cacheBlockSize={50}
onGridReady={onGridReady}
serverSideInfiniteScroll={true}
suppressScrollOnNewData={false}
maxBlocksInCache={1}
/>
</div>
Here are my default column defs:
const defaultColDef = useMemo(
() => ({
sortable: true,
filter: true,
resizable: true,
}),
[]
)
Here is the code for datasource and onGridReady:
const datasource = {
getRows(params) {
console.log(JSON.stringify(params.request, null, 1))
const { startRow, sortModel } = params.request
const pageIndex = Math.round(startRow / 50)
const sortKey = sortModel ? parseSort(sortModel) : null
const serverParams = setQueryParams(pageIndex, sortKey)
getCompanies(serverParams)
.then(response => {
params.successCallback(
response.data.companies,
response.data.meta.total_count
)
})
.catch(error => {
console.error(error)
params.failCallback()
})
},
}
const onGridReady = params => {
setGridApi(params)
params.api.setServerSideDatasource(datasource)
}
I'm completely stumped. I've tried setting overflow: scroll on a number of elements in a css file I have linked to the component, but not dice.
I want to do this: each row is a Radio group, each cell is a Radio button, like the picture:
An example of Radio group is like:
<Radio.Group onChange={this.onChange} value={this.state.value}>
<Radio value={1}>A</Radio>
<Radio value={2}>B</Radio>
<Radio value={3}>C</Radio>
<Radio value={4}>D</Radio>
</Radio.Group>
But I don't know how to add a Radio group to wrap each Antd table row?
My current code is:
renderTable() {
let columns = [];
columns.push(
{
title: '',
dataIndex: 'name',
key: 'name',
width: '45vw',
},
);
this.props.task.options.forEach((option, i) => {
columns.push(
{
title: option,
dataIndex: option,
key: option,
className: 'choice-table-column',
render: x => {
return <Radio value={0} />
},
},
);
});
let rowHeaders = [];
this.props.task.extras.forEach((extra, i) => {
rowHeaders.push(
{"name": `${i + 1}. ${extra}`},
);
});
// How can I pass a className to the Header of a Table in antd / Ant Design?
// https://stackoverflow.com/questions/51794977/how-can-i-pass-a-classname-to-the-header-of-a-table-in-antd-ant-design
const tableStyle = css({
'& thead > tr > th': {
textAlign: 'center',
},
'& tbody > tr > td': {
textAlign: 'center',
},
'& tbody > tr > td:first-child': {
textAlign: 'left',
},
});
return (
<div>
<Table className={tableStyle} columns={columns} dataSource={rowHeaders} size="middle" bordered pagination={false} />
</div>
);
}
I don't think it is possible to use radio group for each row, however you can achieve it in a traditional way.
Here is code sample
https://codesandbox.io/s/goofy-benz-12kv5
class App extends React.Component {
state = {
task: { options: [1, 2, 3, 4, 5], extras: [6, 7, 8, 9, 10] },
selected: {}
};
onRadioChange = e => {
let name = e.currentTarget.name;
let value = e.currentTarget.value;
this.setState({
...this.state,
selected: { ...this.state.selected, [name]: value }
});
};
onSubmit = () => {
console.log(this.state.selected);
this.setState({
...this.state,
selected: {}
});
};
render() {
let columns = [];
columns.push({
title: "",
dataIndex: "name",
key: "name",
width: "45vw"
});
this.state.task.options.forEach((option, i) => {
columns.push({
title: option,
key: option,
render: row => {
return (
<input
type="radio"
checked={this.state.selected[row.name] == option}
onChange={this.onRadioChange}
name={row.name}
value={option}
/>
);
}
});
});
let rowHeaders = [];
this.state.task.extras.forEach((extra, i) => {
rowHeaders.push({ name: `${i + 1}.${extra}` });
});
return (
<div>
<Button onClick={this.onSubmit} type="primary">
{" "}
Submit
</Button>
<Table
columns={columns}
dataSource={rowHeaders}
size="middle"
bordered
pagination={false}
/>
<Tag color="red">Selected options</Tag>
<br />
{JSON.stringify(this.state.selected)}
</div>
);
}
}
hi there i had the same problem and base on new updates on antd this way of using is easier
<Table
rowSelection={{
type: "radio",
getCheckboxProps: (record) => {
console.log("record", record);
},
}}
pagination={{ hideOnSinglePage: true }}
columns={columns}
dataSource={data}
/>
example : https://ant.design/components/table/#components-table-demo-row-selection
for hiding table header : https://newbedev.com/javascript-antd-table-hide-table-header-code-example
hope its usefull
I have an antd table with 2 columns which I need to filter on the first, and search text on the second column.
If I remove the line: ...this.getColumnSearchProps('Tags'),
from my code, the application is rendered fine. Please note the tags field is a json array, not a text field, so I guess that has something to do with the error.
import React, { Component } from 'react';
import { Table, Tag, Button, Icon, Input} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import Highlighter from 'react-highlight-words';
class ListPageTemplatesWithSelection extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
filteredInfo: null,
sortedInfo: null,
searchText: ''
};
this.handleChange= this.handleChange.bind(this);
this.clearFilters= this.clearFilters.bind(this);
this.clearAll= this.clearAll.bind(this);
this.getColumnSearchProps= this.getColumnSearchProps.bind(this);
this.handleSearch= this.handleSearch.bind(this);
this.handleReset= this.handleReset.bind(this);
}
handleSearch (selectedKeys, confirm){
confirm();
this.setState({ searchText: selectedKeys[0] });
}
handleReset(clearFilters){
clearFilters();
this.setState({ searchText: '' });
}
getColumnSearchProps = (dataIndex) => ({
filterDropdown: ({
setSelectedKeys, selectedKeys, confirm, clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
ref={node => { this.searchInput = node; }}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm)}
icon="search"
size="small"
style={{ width: 90, marginRight: 8 }}
>
Search
</Button>
<Button
onClick={() => this.handleReset(clearFilters)}
size="small"
style={{ width: 90 }}
>
Reset
</Button>
</div>
),
filterIcon: filtered => <Icon type="search" style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) => record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text) => (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[this.state.searchText]}
autoEscape
textToHighlight={text.toString()}
/>
),
})
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.SiteType || null,
onFilter: (value, record) => record.SiteType.includes(value),
},{
title: 'Tags',
key: 'Tags',
dataIndex: 'Tags',
...this.getColumnSearchProps('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;
However with that line, the application stops rendering and I got multiple errors like this:
Error on interface:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
▶ 23 stack frames were collapsed.
AsyncFunc._callee$
src/helpers/AsyncFunc.js:26
23 | const { default: Component } = await importComponent();
24 | Nprogress.done();
25 | if (this.mounted) {
> 26 | this.setState({
27 | component: <Component {...this.props} />
28 | });
29 | }
what can I do to fix it?
You need to send the current version to the front-end somehow. Is it metadata that can be retrieved from your azure pipeline? Do you perhaps have pre or postbuild scripts? These could update a variable (++) in a database that you could either retrieve from the front-end with an ajax call or send along when downloading the Js bundle from your server.
When you have retrieved the data somewhere somehow in your back-end, you may also consider passing the version number as a header in the http response.
I am receiving there error "Maximum Call Stack Size Exceeded". After adding a conditional to the navigation file in my code. The stack I am using is React-Native with Expo, Redux, GraphQL and my navigation library is react-navigation. I am working on iOS simulator. This is the code that breaks the
app:
//navigations.js
if (!this.props.user.isAuthenticated) {
return <AuthenticationScreen />;
}
and this is the entirety of the navigations.js
import React, { Component } from "react";
import {
addNavigationHelpers,
StackNavigator,
TabNavigator
} from "react-navigation";
import { connect } from "react-redux";
import { FontAwesome } from "#expo/vector-icons";
import HomeScreen from "./screens/HomeScreen";
import ExploreScreen from "./screens/ExploreScreen";
import FavoritesScreen from "./screens/FavoritesScreen";
import ProfileScreen from "./screens/ProfileScreen";
import AuthenticationScreen from "./screens/AuthenticationScreen";
import { colors } from "./utils/constants";
const TAB_ICON_SIZE = 20;
const Tabs = TabNavigator(
{
Home: {
screen: HomeScreen,
navigationOptions: () => ({
headerTitle: "Saga",
tabBarIcon: ({ tintColor }) => (
<FontAwesome size={TAB_ICON_SIZE} color={tintColor} name="home" />
)
})
},
Explore: {
screen: ExploreScreen,
navigationOptions: () => ({
headerTitle: "Explore",
tabBarIcon: ({ tintColor }) => (
<FontAwesome size={TAB_ICON_SIZE} color={tintColor} name="search" />
)
})
},
Favorites: {
screen: FavoritesScreen,
navigationOptions: () => ({
headerTitle: "Favorites",
tabBarIcon: ({ tintColor }) => (
<FontAwesome
size={TAB_ICON_SIZE}
color={tintColor}
name="file-video-o"
/>
)
})
},
Profile: {
screen: ProfileScreen,
navigationOptions: () => ({
headerTitle: "Profile",
tabBarIcon: ({ tintColor }) => (
<FontAwesome size={TAB_ICON_SIZE} color={tintColor} name="user" />
)
})
}
},
{
lazy: true,
tabBarPosition: "bottom",
swipeEnabled: false,
tabBarOptions: {
showIcon: true,
showLabel: false,
activeTintColor: colors.PRIMARY,
inactiveTintColor: colors.LIGHT_GRAY,
style: {
backgroundColor: colors.BASE_GRAY,
height: 50,
paddingVertical: 5
}
}
}
);
const AppMainNav = StackNavigator(
{
Home: {
screen: Tabs
}
},
{
cardStyle: {
backgroundColor: "#F1F6FA"
},
navigationOptions: () => ({
headerStyle: {
backgroundColor: colors.BASE_GRAY
},
headerTitleStyle: {
fontWeight: "bold",
fontSize: 24,
color: colors.BLUE
}
})
}
);
class AppNavigator extends Component {
render() {
const nav = addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
});
if (!this.props.user.isAuthenticated) {
return <AuthenticationScreen />;
}
return <AppMainNav navigation={nav} />;
}
}
export default connect(state => ({
nav: state.nav,
user: state.user
}))(AppNavigator);
export const router = AppMainNav.router;
For good measure this is the user.js reducer:
const initialState = {
token: null,
isAuthenticated: false,
info: null
};
export default (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
I can't see what would be causing an infinite loop.
Update #1
AuthenticationScreen.js (Minus Imports)
const Root = styled.View``;
const T = styled.Text``;
class AuthenticationScreen extends Component {
state = {};
render() {
return (
<Root>
<T>AuthenticationScreen</T>
</Root>
);
}
}
export default AuthenticationScreen;
As I understand it, you've isolated the problem to this code...
if (!this.props.user.isAuthenticated) {
return <AuthenticationScreen />;
}
return <AppMainNav navigation={nav} />;
And if you remove...
if (!this.props.user.isAuthenticated) {
return <AuthenticationScreen />;
}
it works fine, which means <AppMainNav> must be fine. So the infinite loop must be in your <AuthenticationScreen /> code.
I'm trying to use Parse as the data provider for a ListView in a Reactive Native app. I have followed the Parse guide regarding subscribing to a query but for some unknown reason the the data source is empty. I have verified and writing a test object to Parse works fine.
It seems that observe() should be called before getInitialState() or am I missing something?
'use strict';
var React = require('react-native');
var Strings = require('./LocalizedStrings');
var Parse = require('parse').Parse;
var ParseReact = require('parse-react');
Parse.initialize("api_key_here", "api_key_here");
/*
var TestObject = Parse.Object.extend("TestObject");
var testObject = new TestObject();
testObject.save({foo: "bar"}).then(function(object) {
alert("yay! it worked");
});
*/
var {
View,
Text,
ListView,
StyleSheet
} = React;
var styles = StyleSheet.create({
mainContainer: {
flex: 1,
padding: 30,
marginTop: 65,
flexDirection: 'column',
justifyContent: 'center',
backgroundColor: '#fff'
},
title: {
marginBottom: 20,
fontSize: 22,
textAlign: 'center',
color: '#000'
},
});
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}) // assumes immutable objects
var WorkoutList = React.createClass({
mixins: [ParseReact.Mixin],
observe: function() {
return {
workouts: (new Parse.Query("Workout")).descending("createdAt")
};
},
getInitialState: function() {
return {dataSource: ds.cloneWithRows(this.data.workouts)}
},
renderRow: function() {
return (<View><Text>Testing</Text></View>)
},
render: function() {
return (
<View style = {{flex: 1, flexDirection: 'column'}}>
{Strings.workoutsTabTitle}
<ListView
ref = "listview"
dataSource = {this.state.dataSource}
renderRow = {this.renderRow}
automaticallyAdjustContentInsets = {false}
keyboardDismissMode = "onDrag"
keyboardShouldPersistTaps = {true}
showsVerticalScrollIndicator = {true}
style = {styles.mainContainer}
/>
</View>
)
}
})
module.exports = WorkoutList;
I didn't use ParseReact but the Parse Rest API to fetch data from Parse. The following code is called from componentDidMount.
fetch("https://api.parse.com/1/classes/Workout", {
headers: {
"X-Parse-Application-Id": "Your application Id",
"X-Parse-REST-API-Key": "Your API Key"
}
})
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.results),
loaded: true,
})
})
.catch(function(error) {
console.log(error)
})
.done();
Using this approach you need to wait until the data is loaded before displaying the ListView. Use this.state.loaded to know when this is the case.
This works too.
observe: function() {
return {
user: ParseReact.currentUser,
abc: (new Parse.Query('abc')).descending('createdAt')
};
},
getInitialState: function () {
return {
dataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}),
};
},
render: function() {
return (
<View style={styles.full}>
<ListView
dataSource={this.state.dataSource.cloneWithRows(this.data.abc)}
renderRow={this.renderRow}
/>
</View>
);
},
Hope it helps! Cheers!