I want to use react-select as a custom cell editor for ag-grid
Here is the code sandbox link
You can download the source here
npm install
npm start
I've removed all the css so it looks so plain, but it still does the job.
Here is my package.json
{
"name": "Test",
"version": "1.5.0",
"private": true,
"dependencies": {
"react": "16.8.1",
"react-dom": "16.8.1",
"react-select": "^2.4.1",
"react-scripts": "2.1.5",
"ag-grid-community": "20.1.0",
"ag-grid-react": "20.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"deploy": "npm run build",
"lint:check": "eslint . --ext=js,jsx; exit 0",
"lint:fix": "eslint . --ext=js,jsx --fix; exit 0",
"install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install && npm start"
},
"optionalDependencies": {
"@types/googlemaps": "3.30.16",
"@types/markerclustererplus": "2.1.33",
"ajv": "6.9.1",
"prettier": "1.16.4"
},
"devDependencies": {
"eslint-config-prettier": "4.0.0",
"eslint-plugin-prettier": "3.0.1"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
index.js
import React from "react";
import ReactDOM from "react-dom";
import Grid from './app/AgGrid'
const colDefs = [
{
headerName : "Test",
field : "test",
editor : "manyToOne",
editable : true,
suppressKeyboardEvent : function suppressEnter(params) {
let KEY_ENTER = 13;
let KEY_LEFT = 37;
let KEY_UP = 38;
let KEY_RIGHT = 39;
let KEY_DOWN = 40;
var event = params.event;
var key = event.which;
var editingKeys = [KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_ENTER];
var suppress = params.editing && editingKeys.indexOf(key) >= 0;
console.log("suppress "+suppress);
return suppress;
},
cellEditor : "reactSelectCellEditor",
},
]
ReactDOM.render(
<Grid
readOnly={false}
field={null}
columnDefs={colDefs}
editable={true}
editingMode={null}
rowData={[]}
/>
,
document.getElementById("root")
);
AgGrid.js
import React, { Component } from 'react'
import { AgGridReact } from 'ag-grid-react'
import 'ag-grid-community/dist/styles/ag-grid.css'
import ReactSelectCellEditor from './ReactSelectCellEditor'
class Grid extends Component {
constructor(props) {
super(props);
this.state = {
columnDefs : props.columnDefs,
rowData : [],
isFormOpen : false,
editedData : {
data : [],
rowIndex : null,
columns : []
},
frameworkComponents:{
'reactSelectCellEditor': ReactSelectCellEditor,
},
}
}
updateRowData = (newData,index) => {
var unsavedData = this.state.rowData.map((row,i)=>{
if (i === index) {
return newData
}
else{
return row
}
})
this.setState({rowData:unsavedData})
}
onGridReady = params => {
}
createData = () => {
let fields = this.state.columnDefs.map(column=>{
return column.field
})
let newData = {}
fields.map(field=>{
if (field !== "id") {
newData[field] = ""
}
})
newData["delete"] = false
this.setState({rowData:[...this.state.rowData,newData]})
}
toggleModal = () => {
this.setState({isFormOpen:!this.state.isFormOpen})
}
setEditedData = (data) => {
console.log("editedData",data)
this.setState({editedData:data})
}
render() {
var defaultColDef = {
editable:this.state.editable,
sortable:false
}
var initColDefs = this.state.columnDefs.map(colDef=>{
return {
...colDef,
cellEditorParams:{
...colDef.cellEditorParams,
}
}
})
var renderedColumnDefs = []
if (this.props.editingMode === "inline") {
renderedColumnDefs = [
...initColDefs
]
}
else if (this.props.readOnly) {
renderedColumnDefs = [...initColDefs]
}
else{
defaultColDef = {
editable:false,
sortable:false
}
renderedColumnDefs = [
...initColDefs,
]
}
var renderedRowData = []
this.state.rowData.map(row=>{
if (row.delete !== true) {
renderedRowData.push(row)
}
})
return (
<React.Fragment>
<AgGridReact
defaultColDef = {defaultColDef}
columnDefs = {renderedColumnDefs}
rowData = {renderedRowData}
onGridReady = {this.onGridReady}
onCellValueChanged = {this.handleChange}
frameworkComponents = {this.state.frameworkComponents}
singleClickEdit = {true}
stopEditingWhenGridLosesFocus = {true}
reactNext={true}
/>
<div className="addButton" style={{padding:'5px', border: '1px solid black'}} onClick={()=>this.createData()}>Add</div>
</React.Fragment>
);
}
}
export default Grid
And ReactSelectCellEditor.js
import React, { Component } from 'react'
import CreatableSelect from "react-select/lib/Creatable"
import { components } from 'react-select'
import ReactDOM from 'react-dom'
const colourOptions = [
{ value: 'red', label: 'Red' },
{ value: 'blue', label: 'Blue' },
{ value: 'yellow', label: 'Yellow' }
]
class ReactSelectCellEditor extends Component {
constructor(props) {
super(props);
this.state = {
value : null
}
this.handleChange = this.handleChange.bind(this)
}
componentDidMount() {
}
handleCreateOption = (inputValue:any) => {
}
handleChange(selected){
this.setState({
value : selected.value
})
}
afterGuiAttached(param) {
let self = this;
this.Ref.addEventListener('keydown', function (event) {
// let key = event.which || event.keyCode
// if (key === 37 || key === 38 || key === 39 || key === 40 || key === 13) {
// event.stopPropagation();
// console.log("onKeyDown "+key);
// }
});
this.SelectRef.focus()
}
formatCreate = (inputValue) => {
return (<p> Add: {inputValue}</p>);
};
isPopup(){
return true
}
getValue() {
return this.state.value
}
render() {
return (
<div ref = { ref => { this.Ref = ref }} style={{width: '200px'}}>
<CreatableSelect
onChange = {this.handleChange}
options = {colourOptions}
formatCreateLabel = {this.formatCreate}
createOptionPosition = {"first"}
ref = { ref => { this.SelectRef = ref }}
/>
</div>
);
}
}
export default ReactSelectCellEditor
As you can see, a simple bare bone combined implementation between ag-grid and popped up (not in cell) react-select.
It works fine, except the default navigation key event of ag-grid hinders react-select from working flawlessly.
Now I've read about the solutions to this here in ag-grid documentation of keyboard navigation while editing
Here is the thing:
Firstly, I have tried solution 1 which is to stop event propagation of my custom cell editor. It works, but strangely although I stop the event bubbling in the container of react-select, not the react-select itself, as you can see from the commented out script in ReactSelectCellEditor.js, somehow React Select Key Event also stops executing. I don't know why react select event which is a child element of the div stops executing when I stop the event propagation in the parent element. Isn't the event supposed to bubble up from child to parent not the other way around? So first solution is a no go.
As for the second solution the one that is strange is ag-grid itself. As the documentation says, ag-grid allows us to intercept the key event by defining a function for suppressKeyboardEvent from the column definition. It works if the editing mode is not popup. But in my case, I use the popup mode, and in popup mode I find the suppressKeyboardEvent isn't triggered while editing, it isn't even called at all.
Now I'm in a deadlock. In the first solution the react-select behaves weirdly and in the second one it is the ag-grid that behaves weirdly. How do I solve this?
Additional find
After searching through the internet it looks like React Select uses SyntheticKeyboardEvent
which, if I'm not wrong, executes AFTER events have already gone through a first capture/bubbling cycle across the DOM tree. So if I stop the propagation SyntheticKeyboardEvent
won't be triggered. But if I don't stop the propagation ag-grid which uses the native event listener will trigger its default navigation function and kill the react-select component. Now is this it for me? A dead end?