Search code examples
javascriptreactjstabulator

TypeError: Cannot read property ' ' of undefined via useEffect and Tabulator


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>
)

Solution

  • 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]);