I have a react project where I'm trying to separate logic from the UI by moving it to a hook. The hook in question collects paged data from an RTK Query and aggregates it into a state variable transactions. If I setTransactions as this aggregate, I don't have a problem, but if I try to change it in any way, I see the component rerender many times, causing a Maximum Depth Exceeded error.
I've distilled this problem down to the bare essentials, removing the UI and all logic not causing the error.
AccountTransactions component:
export default function AccountTransactions() {
const { transactions, handleMoreTransactionsClick, handleDeleteTransaction } = useTransactions(68);
console.log('transactions', transactions); // I see this log many times
return (
<div></div>
);
}
TransactionsHook custom hook:
export default function useTransactions (accountId) {
const [page, setPage] = useState(1);
const [transactions, setTransactions] = useState([]);
// load transactions
const {
data = [],
isSuccess,
} = useGetTransactionsQuery({accountId: accountId, page: page});
useEffect(() => {
// setTransactions(transactions); // this doesn't cause rerenders
setTransactions([]); // this causes rerenders
}, [data]);
return { transactions };
}
I'm using data as a dependency for the hook's useEffect because isLoading remains true after the first page is loaded and subsequent pages won't load. However, some combination of using data as a dependency and changing transactions triggers a rerender of the component. Even more confusing for me is that in this cut-down code, transactions is [], but assigning it a new value of [] also causes the rerender.
For completeness, here's the RTK reducer, though I don't believe that's contributing to the problem.
getTransactions: build.query({
query: ({accountId, page}) => ({
url: `/api/transactions/account/${accountId}`,
method: 'get',
params: {page}
}),
providesTags: (result = [], error, arg) => [
'Transaction',
...result.data.map(({ id }) => ({ type: 'Transaction', id })),
],
}),
I'm still getting my head around hooks (and even the rendering process) but I can't see how I've broken any of the rules. Can anyone else spot anything?
setTransactions(transactions);doesn't cause render, because previous statetransactionsand new statetransactionsare the same object (same object reference). Therefore hookuseGetTransactionsQuerycalled only once.setTransactions([])causes render loop, because[]creates new object every time(state changed), which causes render, which causesuseGetTransactionsQueryhook call, which changesdataand causes effect.setTransactions([])(state changed) => render =>useGetTransactionsQueryhook call =>datachanged => effect called =>setTransactions([])(state changed)