How to fix React.useEffect and useCallback circular loop when updating a state?

I’m fetching some data from an API, but since I am using this data to update a state within a useEffect, this state becomes a required dependency, which causes a circular loop.

state is updated -> useEffect is called -> state is updated ...

I read a lot of answers and articles about that, but I couldn’t find an answer to my specific question.

This is my initial code:

let [state, setState] = React.useState<IDataSource[]>([])

React.useEffect(() => {
    let dataSource: IDataSource[] = []
   
    dataFetched.forEach((item, index) => {
        // Some logic
    })
    
    setState(state.concat(dataSource))

}, [dataFetched, state])

Then I decided to update the state using a function called by useEffect and passing an argument:

let [state, setState] = React.useState<IDataSource[]>([])

const updateData = (arg: IDataSource[] => {
    setData(state.concat(arg))
}

React.useEffect(() => {
    let dataSource: IDataSource[] = []
   
    dataFetched.forEach((item, index) => {
        //Some logic
    })
    
    updateData(dataSource)
}, [dataFetched, updateData])

This works, but since I have updateData as a useEffect depency I have to wrap the function with useCallback:

const updateData = React.useCallback((arg: IDataSource[]) => {
    setData(state.concat(arg))
}, [state])

React.useEffect(() => {
    let dataSource: IDataSource[] = []
   
    dataFetched.forEach((item, index) => {
        //Some logic
    })
    
    updateData(dataSource)
}, [dataFetched, updateData])

But in this case I also have state as a useCallback depency and I’m back to the starting problem, a circular loop.

Apparently I should use React.useRef when I have an array as useEffect dependency, but state is not just an array, it is actually a state, which is being set with useState, so I don’t know how to do that in this case or even if this is possible.

Is there a way to solve that?

Answer

You can remove data from the useEffect array and call setState with an updater function like this setState(prevState => prevState.concat(dataSource))

const [state, setState] = React.useState<IDataSource[]>([])

React.useEffect(() => {
    const dataSource: IDataSource[] = []
   
    dataFetched.forEach((item, index) => {
        // Some logic to fill the dataSource array ??
    })
    
    setState(prevState => prevState.concat(dataSource))
}, [dataFetched])