VDog
VDog

Reputation: 1173

What is the best practice approach to getting data from the redux store into a simple function?

Say I have a function called calculateFees, which needs some data sent from the backend such as consultingFee. It would look something like:

const calculateFees = itemPrice => itemPrice * [consultingFee]

[consultingFee] is a placeholder, as I am not sure how to get it from the redux store. Let's also say that calculateFees is far more complicated, that has about 10+ types of fees, and is used by dozens of components. The options I could think of are:

1) Pass in all the fees from the class that calls it that is connected to the redux state.

(e.g. calculateFees(itemPrice, feeOne, feeTwo, FeeThree, ...)

2) duplicate the code in each class that is connected to the redux state.

3) export the entire store (or part of it), so that the calculateFees function can access the data via import.

4) Not sure if this is even possible, but create a class with static methods, and connect it to the redux store. And then use it such as MyCompanyFees.calculateFees.

5) Create a thunk that solely pulls data out of state, and returns the calculated fees. It doesn't seem correct to use this, as it isn't asynchronous, nor does it update the state tree.

I wanted to get some guidance / suggestions on the best practices approach, as I am not a fan of 1, 2 or 3.

Upvotes: 0

Views: 68

Answers (2)

simka
simka

Reputation: 901

I'd say it depends a lot on a specific use case, like how often these values (meaning inputs to calculate function) can change (is it shopping-cart kind of thing or more like a generated report kind of thing, when you fetch the data once and that's it) and if it's just one calculation per view or more of them with different inputs (for example you have to calculate the fee for every row in a table).

First of all, calculation should be a separate function that gets all the data it needs as arguments (this way you can also unit test it easily) and doesn't care if it's used in React app or if you're using Redux or whatever. If you think it will be called multiple times with the same arguments you could also read up on concept called memoization, but I guess in majority of cases memoization should happen outside of this function.

Now, assuming you already have the rest of data (the consultingFee placeholder you mentioned) in your store and want to get calculated value after getting a response from your backend. In this case you can extract values from store as shown in bsapaka's answer and put calculated value in redux store, that you access where you need it. This is a enough if you make the request once, display calculated value and that's it, you know that if you need to calculate it again you will have different input values and will have to call backend again and you don't need to cache the result although I'd argue that if that's the case you shouldn't store the calculated value in your store at all.

You could also use selector further down the way in mapStateToProps function, reselect library would be useful for that (be sure to read "Sharing Selectors with Props Across Multiple Component Instances" section of README). This way, you have component that dispatches action that gets data you need from backend and puts it in redux store. In mapStateToProps you use memoized selector that collects all the data it needs and calculates result, that won't be recalculated unless input (so relevant values from redux store in this case) change. I'd prefer this approach, as it reduces side-effects in redux and feels more declarative for me, but I also prefer to use redux as kind of cache with raw values and if I need to derive values from it I use memoized selectors.

Upvotes: 0

brietsparks
brietsparks

Reputation: 5016

A simple calculation/utility function should not be aware of where its data comes from and should just accept it as an argument:

const calculateFees = (itemPrice, consultingFee) => itemPrice * consultingFee

If the data comes from the store, then calculateFees should be called in a selector.

Selector:

const getCalculatedFees = (state, { itemId }) => {
    const itemPrice = state.items[itemId].price;
    const consultingFee = state.consultingFee;

    return calculateFees(itemPrice, consultingFee);
}

Then the selector can be used by components or action-related functions like thunks and sagas.

Component:

connect(
    state => ({
        calculatedFees: getCalculatedFees(state)
    })
)(MyComponent)

Thunk:

const myAction = itemId => (dispatch, getState) => {
    const calculatedFee = getCalculatedFees(getState(), { itemId });

    // do stuff...
    dispatch(doSomethingElse(calculatedFee));
}

Saga:

function* mySaga({ itemId }) {
    const calculatedFee = yield select(getCalculatedFees, { itemId });

    // ...
}

Upvotes: 1

Related Questions