Search code examples
reduxlodashdispatchthrottling

lodash throttle redux dispatch Uncaught TypeError: Expected a function


TL;DR I am running into strangeness with creating a throttled function where I need to pass parameters to this method manually while creating the throttled function. I can do this by changing my original function, but I don't want to do this for all my old legacy functions/action creators.

The function passed to throttle:

Note: these are redux action creators.

const changeValue = (value: string, otherValue?: boolean) => {
    return {
        type: "SOME_TYPE",
        payload: {
            value: value,
            otherValue: otherValue || false
        }
    };
};

The creation of the throttled function:

const throttledMethod = (value: number, otherValue:boolean ) => {
    throttle(changeValue(Number(value),otherValue), 175, { "trailing": false });
};

This normally expects a function to be passed and not the invocation, but then how can I pass parameters unless I am invoking? I don't want to curry in this situation.

The error produced:

node_modules\lodash\throttle.js:56 Uncaught TypeError: Expected a function
    at throttle (node_modules\lodash\throttle.js:56)

When break pointed at line 56 in lodash and inspected we see that the value of the object returned from valueChanged:

{
    type: "SOME_TYPE",
    payload: {
    value: value,
    otherValue: otherValue || false
}

That is all fair enough.

The strangeness is that we get this to work by doing some relatively dirty manual work in the Redux action creator. I call this dirty because I don't want to change all of my old legacy action creators for this new thing I am doing.

When I manually wrap my action creator like so with the Redux.js dispatch method everything starts working as expected.:

const changeValue = (value: string, otherValue?: boolean) => {
    return function (dispatch) {
        dispatch({
            type: "SOME_TYPE",
            payload: {
                value: value,
                otherValue: otherValue || false
            }
        });
    };
};

So in the end this isn't really so much of an issue with Lodash, but still maybe we could address how to properly pass parameters to a method that needs to be passed to throttled without changing my all of my original methods or currying. Currying confuses a lot of people and in some situations I do somewhat want to pander to the less literate.

Perhaps we can create a wrapper that handles manually wrapping dispatch when passing to lodash throttle or other methods. I think something like that would be ideal, but can't imagine what this wrapper would look like.

Perhaps this is something I might ask the authors about, but first I wanted to attempt "userland".


Solution

  • First, you can get throttle to work by wrapping the invocation in an anonymous function:

    const throttledMethod = (value: number, otherValue: boolean) => {
        throttle(
            () => changeValue(Number(value), otherValue), 
            175, 
            { "trailing": false }
        );
    };
    

    However, calling throttledMethod() will just invoke throttle which returns a throttled version of changeValue. It doesn't invoke the resulting throttled function. Additionally, changeValue is just an action creator--it doesn't actually do anything.

    Your second action creator that returns (dispatch) => {} is using redux-thunk. That's good because we're going to need it.

    You can get the functionality you're looking for with this:

    // we only want to set up the throttling once
    const throttledChangeValue = throttle(
        (dispatch, value, otherValue) => dispatch(changeValue(Number(value), otherValue)), 
        175, 
        { "trailing": false }
    );
    
    // this is using thunk to dispatch the throttled action
    const throttledPayOff = (value, otherValue) => dispatch => throttledChangeValue(dispatch, value, otherValue);