useState hook - state gets lost i.e. resets to initial value

Viewed 3276

I am trying to initialize the state during the mounting cycle, and then do something with it on each update.

However, the state gets reset somehow? I don't understand why.

const [journalItems, setJournalItems] = useState([]);

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(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
}, [journalItems]);

function handleTableRowClick(journalItemId) {
    console.log("handletablerowclick journalitems", journalItems) // State is lost, resets to []
}

Results form the console logs...

useEffect journalItems []
useEffect journalItems (7) [{…}, {…}, {…}, {…}, {…}, {…}, {…}]

tabulator journalitems []
handleTableRowClick journalitems []
2 Answers

I managed to solve it in a weird way 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]);

I believe this is happening because the initial value for journalItems is being closed over inside the function defined for the rowClick property.

Because the effect containing the table.current is only run once, that table.current value gets all initial values and never gets updated with new handlers for the rowClick prop.

I suspect that you might need to make the changes below to get things moving along:

const [journalItems, setJournalItems] = useState([]);

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);
}, [props.match.params.key]);

// Re-create a new `table.current` value (including all handlers) when `journalItems` changes
table.current = useMemo(() => {
  return new Tabulator(refTable.current, {
        rowClick: function (e, row) {
            console.log("tabulator journalitems", journalItems)
            handleTableRowClick(row._row.data.id)
        },
        columns: [
            { title: "Компанија", field: "companyName" },
            { title: "Документ", field: "documentKey" },
        ],
    });
}, [journalItems]);

useEffect(() => {
    console.log("useEffect journalItems", journalItems) 
    table.current.replaceData(journalItems)              
}, [journalItems]);

const handleTableRowClick = useCallback((journalItemId) => {
  console.log("handletablerowclick journalitems", journalItems);
}, [journalItems]);

Don't forget to update the dependency array of your first effect. All variables from the outer scope that are referenced inside your effect must be in this array.

Related