Currently, I am using functional react components with react hook useState
and an AgGridReact
Component.
I am displaying an AgGridReact
and put a onCellClicked
function on a AgGridColumn
. So far everything is working. In the onCellClicked
function I want to update my state and do something depending on its current value.
Here is the problem:
if I want to use my state get/set (useState
hook) inside of the onCellClicked
function it is not working as expected. For some reason, I can not update my state.
In a react class component it is working.
EDIT: I experimented for a while and found out that in the onCellClicked
function I have only the default value in myText. I can update it once. If I spam the onCellClicked
function it will append the text again to the default value from useState("default myText");
. I would expect that the string would get longer as often I click on the cell. Just as in my Class Component example.
If I use a simple button outside of the AgGridReact
<button onClick={() => setMyText(myText + ", test ")}>add something to myText state</button>
it is working as expected, the string gets longer every time I click on my <button>
. If I change the state of myText via the <button>
outside of the AgGridReact
and then click on the cell function again the state previously setted through my <button>
is lost.
Example react hook component:
import React, { useState } from 'react';
import { AgGridColumn, AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
function App() {
const [myText, setMyText] = useState("default myText");
const [todoListRowData, setTodoListRowData] = useState([]);
// ....fetch data and set the todoListRowData state....
const myCellClickFunction = (params, x) => {
// here is the problem:
// no matter how often I click in the cell myText is every time the default value 'default myText'
// EDIT: I found out I can update the state here but only from the initial default value once, myText is on every cell click again "default myText" and will be concatenated with "hookCellClicked". So every time I click this cell the state gets again "default myTexthookCellClicked"
console.log(myText);
setMyText(myText + "hookCellClicked");
}
return (
<div className="ag-theme-alpine" style={{ height: '600px', width: '100%' }}>
<AgGridReact rowData={todoListRowData} >
<AgGridColumn headerName="ID" field="id" maxWidth="50"></AgGridColumn>
<AgGridColumn headerName="UserId" field="userId" maxWidth="85"></AgGridColumn>
<AgGridColumn headerName="Title" field="title" minWidth="555"></AgGridColumn>
<AgGridColumn headerName="completed" field="completed"></AgGridColumn>
<AgGridColumn headerName="Testcol" onCellClicked={(params) => myCellClickFunction(params)}></AgGridColumn>
</AgGridReact>
</div>
}
export default App;
If I do the exact same thing in a class component it is working fine.
Example Class Component:
import React from 'react';
import { AgGridColumn, AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
class MyClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
myClassComponentRowData: [],
testState: "defaul testState"
};
}
// ....fetch data and set ag grid rowData state....
handleCellClick = (params) => {
// here everything works just fine and as expected
// every time I click on the cell the state testState updates and it is added ", handleCellClick" every time
console.log(this.state.testState);
this.setState({testState: this.state.testState + ", handleCellClick"});
}
render() {
return <div className="ag-theme-alpine" style={{ height: '600px', width: '100%' }}>
<AgGridReact rowData={this.state.myClassComponentRowData} >
<AgGridColumn headerName="ID" field="id" maxWidth="50"></AgGridColumn>
<AgGridColumn headerName="UserId" field="userId" maxWidth="85"></AgGridColumn>
<AgGridColumn headerName="Title" field="title" minWidth="555"></AgGridColumn>
<AgGridColumn headerName="completed" field="completed"></AgGridColumn>
<AgGridColumn headerName="Testcol" onCellClicked={(params) => this.handleCellClick(params)}></AgGridColumn>
</AgGridReact>
</div>
}
}
export default MyClassComponent;
Am I doing something wrong? I want to use a functional component with react hooks.
There is nothing wrong with the code in your question except that the callback myCellClickFunction
references the old state myText
which is captured in the previous render call. If you log in the render method, you can see the state is updated properly. This problem is called stale closure.
function App() {
const [myText, setMyText] = useState("default myText");
const [todoListRowData, setTodoListRowData] = useState(rowData);
console.log("render", myText); // prints the latest myText state
...
}
You can see my other answer here about how to get the latest state in callback when using React hook. Here is an example for you to try that out.
function useExtendedState(initialState) {
const [state, setState] = React.useState(initialState);
const getLatestState = () => {
return new Promise((resolve, reject) => {
setState((s) => {
resolve(s);
return s;
});
});
};
return [state, setState, getLatestState];
}
function App() {
const [myText, setMyText, getLatestMyText] = useExtendedState(
"default myText"
);
const myCellClickFunction = async (params) => {
setMyText(params.value);
const text = await getLatestMyText();
console.log("get latest state in callback", text);
};
...
}