DEV Community

AVI
AVI

Posted on • Updated on

A pattern to write clean Redux connected components

Some components have a lot of side-effect functionality going on. Some of my projects use Redux with redux-saga for all side effect management. I came across a lot of code on Redux web apps where the mapDispatchToProps function looked really huge and weird. Moreover, I've seen a lot of people manually call

  dispatch(someActionCreator);
Enter fullscreen mode Exit fullscreen mode

throughout their components; which is again not a very nice looking thing to do.

I think useSelector and useDispatch are great when your component has only a few selectors and actions used; but otherwise I don't see myself littering the first 20 lines of my React component with useSelectors; and then having to wrap each actionCreator in a dispatch throughout. Here's how I do things:

// makeActionCreatorsToBind.js

import { pick } from 'ramda';

export default ({ actionCreators, keysToPick }) => pick(keysToPick, actionCreators);

Enter fullscreen mode Exit fullscreen mode
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { compose, assoc } from 'ramda';
import { createStructuredSelector } from 'reselect';

import CheckoutPage from 'modules/Checkout/';

import * as checkoutActionCreators from 'modules/Checkout/redux/actions';

import { makeSelectCart } from 'modules/Cart/redux/selectors';

import makeActionCreatorsToBind from './makeActionCreatorsToBind';


function Checkout(props) {
  return (
    <CheckoutPage
      {...props}
    />
  );
}

/* add some other action creators from different modules,
* I'm using Ramda here just so people reading this can
* pick up composition patterns.
*/
const actionCreators = compose(
  assoc('getCreditBalanceRequest', getCreditBalanceRequest),
  assoc('reinitializeCart', reinitializeCart),
))(checkoutActionCreators);


// another way of doing the same thing:
const actionCreatorsMadeWithoutRamda = {
  ...checkoutActionCreators,
  getCreditBalanceRequest,
  reinitializeCart,
};

const actionCreatorsToBind = makeActionCreatorsToBind({
  actionCreators,
  keysToPick: [
    'reinitializeCart',
    'getCreditBalanceRequest',
    'shippingRequest',
    'createOrderRequest',
    'sendOrderDetails',
    'getTotalsRequest',
    'confirmOrderRequest',
    'reinitializeCheckout',
    'applyAddressData',
    'applyCouponCodeRequest',
  ]
});

const mapDispatchToProps = dispatch => bindActionCreators(actionCreatorsToBind, dispatch);

const mapStateToProps = createStructuredSelector({
  cart: makeSelectCart(),
});

export default connect(mapStateToProps, mapDispatchToProps)(Checkout);

Enter fullscreen mode Exit fullscreen mode

I'll explain bits of the code.

bindActionCreators

Redux exposes this function, which basically can take an object with key-value pairs, where the values are action creators; and decorate all the action creators with dispatch, so that you can call the keys, i.e. the names of those action creators as if they're normal functions.

the Ramda bit

assoc => basically a functional way of doing myObject['propertyName'] = someActionCreator

compose => starting from the last function, returns a function that applies all the functions right to left (bottom to top) on the provided argument.

The ramda bit basically creates an object actionCreators that takes an argument (checkoutActionCreators from a file where action creators are described in this case, and there's like many of them), and adds some other action creators from some other files to that object.

The compose function adds keys reinitializeCart and getCreditBalanceRequest with the values of action creators with the same names; imported from some other module.

We then use this new object to call our makeActionCreatorsToBind function and assign the result to a variable named actionCreatorsToBind. This function takes out just the actions that our component needs and returns an object with our specified keys, ignoring the actions we don't need from checkoutActionCreators.

Now we can pass this new object (named actionCreatorsToBind) to bindActionCreators and voila, everything looks neat and works well.

Oldest comments (0)