In React, Is it possible to get access to Redux action creators from inside a utility function?

Viewed 481

I have a form input validation function that just returns an empty errors object or a populated errors object.

const validate = function validateSomeInput({ field1, field2 }) {
  const errors = {}
  // some logic
  return errors
}

export default validate

Is there a way to hook an action creator into this function?

This is what I am trying to achieve:

const validate = function validateSomeInput({ field1, field2 }) {
  const errors = {}
  // some logic
  props.onErrorEvent('do something')
  return errors
}

export default connect(null, { onErrorEvent })(validate)

My issue is that since it's not a class, I don't have access to this.props, and I have never tried to make a container before that doesn't render any JSX.

I also can't create a callback into the parent because the validate function is controlled by Redux-Form.

It's used like this in the parent container:

export default compose(
  connect(mapStateToProps, {
    onErrorEvent,      // this is the action creator I want to access from validate
    onLoginSuccess,
  }),
  graphql(LOGIN_MUTATION),
  reduxForm({
    validate,           // how do I get an action creator into this function?
    form: 'LoginForm',
  }),
)(LoginForm)

Is it possible to wire an action creator into a utility function that doesn't render JSX and can't callback to parent?

I'd also like to know if this is bordering into any kind of horrendous anti-pattern territory because I can't find any information about allowing random utility functions to call action creators.

I didn't see a solution with bindActionCreators, but maybe I just didn't see how it relates to this scenario.

Is there a way I can create a HOC in this scenario?

3 Answers

You can access the redux store directly yes.

Step 1: Export the store

In the file where you initially configure your store, make sure you export it as well so you can have a reference to it from anywhere inside your app:

import {createStore} from 'redux'

const store = createStore([... your config]);

export default store;

Step 2: Use it anywhere in your code

import {reduxStore} from './store'; // your previous created file

const yourEntireReduxState = reduxStore.getState();
// use it whichever way you want here

Step 3: Dispatch actions

import {reduxStore} from './store'; // your previous created file

store.dispatch({type: 'YOUR_ACTION', payload: {...}});

The validate function receives a second argument, which are the component props. This makes it trivial to pass in the dispatch-bound action creators. See the docs for details, but your code example should work if you examine the 2nd argument:

function validate({ field1, field2 }, {onErrorEvent}) {
  // ...
}

export default compose(
  connect(mapStateToProps, {
    onErrorEvent,      // this is the action creator I want to access from validate
    onLoginSuccess,
  }),
  graphql(LOGIN_MUTATION),
  reduxForm({
    validate,           // how do I get an action creator into this function?
    form: 'LoginForm',
  }),
)(LoginForm)

If you want to insulate the validation function from the action, then use this pattern:

function sendOnError(validate, fields, props) {
  const result = validate(fields, props);
  if (!_.isEmpty(result)) {
    props.onErrorAction(...);
  }
  return result;
}

const myValidator = ({field1, field2}) => ...;

export default compose(
  connect(mapStateToProps, {
    onErrorEvent,      // this is the action creator I want to access from validate
    onLoginSuccess,
  }),
  graphql(LOGIN_MUTATION),
  reduxForm({
    validate: sendOnError.bind(null, myValidator),
    form: 'LoginForm',
  }),
)(LoginForm)

Redux Thunk would be a good way to do this. It's a middleware that allows you to write action creators that return functions, gives you access to the store, and allows you to dispatch other actions from action creators.

import { onErrorEvent } from '../wherever';

const validate = function validateSomeInput({ field1, field2 }) {
  return function(dispatch, getState) {
    const errors = {}
    // some logic
    dispatch(onErrorEvent('do something'))
    return errors
  }
}

Related