Trying to wrap dispatch function in react redux

Viewed 1315

Hi recently I encountered the useDispatch hook that supposed to give me an alternative to mapDispatchToProps, and I found very repetitive to do () => dispatch(action(args)) in each onPress so I started to think about something generic. My goal was to make a hook that uses useDispatch() and wraps the functions that it gets and retuens () => dispatch(theWrappedAction(theActionArgs)) for example if I have an action upCounterActionCreator that is as following:

export const upCounterActionCreator = (count: number = 1): AppActions => {
const action: UpCounterAction = {
    type: 'UP_COUNTER',
    count
};
return action;
};

My goal is to do something like this:
const [upAfterDispatch] = useActions(upCounterActionCreator);

and then I can do:

<Button onPress={upAfterDispatch(1)} title='+' />

What I tried to do is as following:

export const useActions = (...actions: ((...args: any) => AppActions)[]) => {
const dispatch = useDispatch<Dispatch<AppActions>>();
const actionsWithDispach: ((...args: any) => () => (...args: any) => AppActions)[] = [];

actions.forEach(action => {
    actionsWithDispach.push((...args: any) => () => (...args: any) => dispatch(action(...args)));
});
return actionsWithDispach;

};

to put that wrapped function on onPress I need to do

<Button onPress={upAfterDispatch(1)()} title='+' /> - to invoke it, it is not so good option. Then when I do call it the action indeed is being dispatched however when I debug on my payload I have an object insted of count that is as following: it is a class-

enter image description here

What am I doing wrong? What do I need to to in order to:

  1. get the number 1(the count parameter sent in the action payload) instead of the class

  2. invoke the returned functions from useActions and not call it like this onPress={upAfterDispatch(1)()

** I think that the object received in the args is the react native onPress event, how to avoid it overriding my count argument? Thanks ahead!

2 Answers

I think this is what you wanted to do:

export const useActions = (...actions: ((...args: any) => AppActions)[]) => {
  const dispatch = useDispatch<Dispatch<AppActions>>();
  const actionsWithDispach: ((...args: any) => () => (...args: any) => AppActions)[] = [];

  actions.forEach(action => {
    actionsWithDispach.push((...args: any) => () => dispatch(action(...args)));
  });
  return actionsWithDispach;
};

You added an extra (...args: any) => but with the code above you can do onClick={theAction(1)}

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore } = Redux;
const initialState = {
  count: 0,
};
const reducer = (state, { type, payload }) => {
  if (type === 'UP') {
    return { count: state.count + payload };
  }
  return state;
};
const store = createStore(
  reducer,
  { ...initialState },
  window.__REDUX_DEVTOOLS_EXTENSION__ &&
    window.__REDUX_DEVTOOLS_EXTENSION__()
);
//action
const add = (howMuch) => ({ type: 'UP', payload: howMuch });
const useAction = (action) => {
  const dispatch = useDispatch();

  return React.useMemo(
    () => (...args) => () => dispatch(action(...args)),
    [action, dispatch]
  );
};
const Button = React.memo(function Button({ up, howMuch }) {
  const rendered = React.useRef(0);
  rendered.current++;
  return (
    <button onClick={up(howMuch)}>
      Rendered: {rendered.current} times, add {howMuch}
    </button>
  );
});

const App = () => {
  const up = useAction(add);
  const count = useSelector((state) => state.count);
  return (
    <div>
      <h2>count:{count}</h2>
      <Button up={up} howMuch={1} />
      <Button up={up} howMuch={1} />
      <Button up={up} howMuch={1} />
    </div>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>


<div id="root"></div>

type ActionCreator = (...args: any) => AppActions
export const useActions = (...actions: ActionCreator[]) => {
  const dispatch = useDispatch<Dispatch<AppActions>>();
  return actions.map(action => (...args: any) => () => dispatch(action(...args)))
}

Related