0

Can you help me solve useEffect riddles?

I want to satisfy the linter react-hooks/exhaustive-deps rule but also have a component which does not re-render all the time. My component should aggregate data if it is loading for the first time. This data is fetched from redux, then the summation is done and the data is stored back to redux.

As explained in this question, I could move the aggregation to the component where the data is added, but this would force me to update the arrays more often, once either stocks or transaction is added and this even I never view the aggregate.

This question also points to a post by Dan Abramov not to disable the linter rule. Which let me came to the conclusion just leaving something out in the useEffect array is not the way to go — as it is the case in my example.

Now the question is how to solve this?

import React, { useEffect } from "react";
import { useDispatch } from "react-redux";

import { ITransactionArray } from "../../../store/account/types";
import { IStockArray, STOCKS_PUT } from "../../../store/portfolio/types";
import { useTypedSelector } from "../../../store/rootReducer";
import { AppDispatch } from "../../../store/store";

const aggregateStockQuantities = (
    stocks: IStockArray,
    transactions: ITransactionArray
): IStockArray => {
    console.log("run Value/aggregateStockQuantities");

    const newStocks = [...stocks];
    newStocks.forEach((s) => {
        s.quantity = 0;
        return s;
    });

    transactions.forEach((t) => {
        newStocks.forEach((s) => {
            if (s.quantity !== undefined && t.isin === s.isin) {
                s.quantity += t.quantity;
            }

            return s;
        });
    });

    return newStocks;
};

const Test: React.FC = () => {
    const dispatch: AppDispatch = useDispatch();
    const { stocks } = useTypedSelector((state) => state.portfolio);
    const { transactions } = useTypedSelector((state) => state.account);
    const stockQuantities = aggregateStockQuantities(stocks, transactions);

    useEffect(() => {
        dispatch({
            type: STOCKS_PUT,
            payload: stockQuantities,
        });
    }, [dispatch]);
    // only works if I leave out stockQuantities
    // but then warning:  React Hook useEffect has a missing dependency: 'stockQuantities'. Either include it or remove the dependency array  react-hooks/exhaustive-deps


    // Render
}

Update #1: Had to change the aggregateStockQuantities so quantity is null at the start.

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
lony
  • 6,733
  • 11
  • 60
  • 92
  • use reselect to create a memoized version of stockQuantities and add the dependency. – HMR May 10 '20 at 21:15
  • Is this a third party library? I would love to solve this only with react and redux, to better understand the problem. If this is std, could you give an example? – lony May 10 '20 at 21:22
  • If you want to use redux you may want to use composable memoized selectors, [this is all the code in reselect](https://github.com/reduxjs/reselect/blob/master/src/index.js) – HMR May 10 '20 at 21:38
  • Sorry but in my state of mind this confuses me even more, could you add an example how I can change my code to incorporate this? – lony May 10 '20 at 21:40

1 Answers1

1

I would advice making a selector that only selects aggregateStockQuantities from the redux state.

And move the const stockQuantities = aggregateStockQuantities(stocks, transactions); inside the useEffect hook.

Incepter
  • 2,711
  • 15
  • 33
  • `aggregateStockQuantities` is not stored inside redux as a whole. redux only stores an array of stocks including the quantities. I did move the function call inside `useEffect` but had the same problem. Either it rendered infinite or I needed to remove stocks. – lony May 10 '20 at 21:24
  • Then, what the `STOCKS_PUT` actions does exactly ? How it impacts your component ? – Incepter May 10 '20 at 21:26
  • It updates the portfolio store just overwriting all stocks in it. – lony May 10 '20 at 21:38