Here is a codesandbox for the problem.
The example has three parts:
One useEffect instantiates the grid, while another listens for state changes to update the grid.
Everything works fine until a row is clicked in the grid library.
I get this error.
TypeError: Cannot read property 'companyName' of undefined
471 | id={"input-companyName"}
472 | list={"options-companyName"}
473 | className={"form-input"}
> 474 | value={formJournalItems.companyName}
475 | onChange={handleDatalistChange}
476 | onKeyUp={handleKeyUp}
477 | ref={refCompanyName}
325 |
326 | function handleTableRowClick(journalItemId) {
327 | let journalItem = journalItems.filter(item => item.id === journalItemId)[0]
> 328 | setFormJournalItems(journalItem)
329 | }
330 |
331 | function isValidFormInputs() {
Here's the code in question, the rest can be seen in the codesandbox.
let refCompanyName = React.createRef();
let refTable = useRef(null);
let table = useRef(null);
const [journalItems, setJournalItems] = useState([]);
const initialFormJournalItems = {
id: "",
journalId: "",
companyId: "",
companyName: "",
documentKey: "",
documentDate: "",
debitAccountId: "",
debitAccount: "",
debit: "",
creditAccountId: "",
creditAccount: "",
credit: ""
}
const [formJournalItems, setFormJournalItems] = useState(initialFormJournalItems);
useEffect(() => {
fetch(`http://localhost:4000/journals/${props.match.params.key}/items`)
.then(res => res.json())
.then(data => {
setJournalItems(data)
})
.catch(err => err);
table.current = new Tabulator(refTable.current, {
data: journalItems,
height: "100%",
layout: "fitColumns",
rowClick: function (e, row) {
//e - the click event object
//row - row component
console.log("tabulator journalitems", journalItems) // <------ EMPTY []
console.log(row._row.data)
handleTableRowClick(row._row.data.id)
},
columns: [
{ title: "Компанија", field: "companyName" },
{ title: "Документ", field: "documentKey" },
{ title: "Датум", field: "documentDate" },
{ title: "Должи", field: "debitAccount" },
{ title: "Износ", field: "debit" },
{ title: "Побарува", field: "creditAccount" },
{ title: "Износ", field: "credit" },
],
});
}, []);
// Updates the tabulator table on item change
useEffect(() => {
console.log("useEffect journalItems", journalItems) // <------ POPULATED
console.log("useEffect refTable", refTable)
console.log("useEffect table", table)
table.current.replaceData(journalItems)
}, [journalItems]);
function handleTableRowClick(journalItemId) {
let journalItem = journalItems.filter(item => item.id === journalItemId)[0]
setFormJournalItems(journalItem)
}
return (
<div>
<input
type={"text"}
name={"companyName"}
id={"input-companyName"}
list={"options-companyName"}
className={"form-input"}
value={formJournalItems.companyName}
onChange={handleDatalistChange}
onKeyUp={handleKeyUp}
ref={refCompanyName}
multiple
/>
</div>
)
I managed to solve it with useRef
for the function variable, and by moving the function definition inside the useEffect
?
const [journalItems, setJournalItems] = useState([]);
let handleTableRowClick = useRef(null);
useEffect(() => {
fetch(`http://localhost:4000/journals/${props.match.params.key}/items`)
.then(res => res.json())
.then(data => {
setJournalItems(data) // Sets the state when the AJAX completes
})
.catch(err => err);
table.current = new Tabulator(refTable.current, {
rowClick: function (e, row) {
console.log("tabulator journalitems", journalItems) // State is lost returns []
handleTableRowClick.current(row._row.data.id)
},
columns: [
{ title: "Компанија", field: "companyName" },
{ title: "Документ", field: "documentKey" },
],
});
}, []);
useEffect(() => {
console.log("useEffect journalItems", journalItems) // Works fine
table.current.replaceData(journalItems) // Uses the new state
handleTableRowClick.current = (journalItemId) => {
console.log("handletablerowclick journalItems", journalItems)
// Find the clicked row from all the rows
let journalItem = journalItems.filter(item => item.id === journalItemId)[0]
setFormJournalItems(journalItem)
}
}, [journalItems]);