30

In a redux-form handleSubmit function, which makes an Ajax call, some properties from the Redux state are needed in the Ajax (e.g. user ID).

This would be easy enough if I wanted to define handleSubmit in the form's component: Just call mapStateToProps to pass in whatever I need, and read it from this.props in my handleSubmit.

However, like a good React-Redux developer I write separate view components and containers, so my view components can be simple with little or no behavior. Therefore, I want to define handleSubmit in my container.

Again, simple - redux-form is set up to do that. Define an onSubmit in mapDispatchToProps, and the form will call it automatically.

Except, oh wait, in mapDispatchToProps there's no way to access redux state.

Okay, no problem, I'll just pass the props I need into handleSubmit along with the form values.

Hmm, wait, it is impossible to pass any data into handleSubmit using this mechanism! You get one parameter, values, and there's no way to add another parameter.

This leaves the following unattractive alternatives (of which I can verify that 1 and 2 work):

1 Define a submit handler in the form component, and from there call my own custom submit handler function passed in from mapDispatchToProps. This is okay, but requires my component to know some props from the redux state that aren't required to display the form. (This issue is not unique to redux-form.)

dumbSubmit(values)
{
  const { mySubmitHandler, user} = this.props;
  mySubmitHandler(values, user);
}

<form onSubmit={ handleSubmit(this.dumbSubmit.bind(this)) }>

Or, more concisely, this can all be combined into an arrow function:

<form onSubmit={ handleSubmit((values)=>{mySubmitHandler(values, this.props.user);}

Then to make this handler completely agnostic, it could just pass the entire this.props back to the custom handler:

<form onSubmit={ handleSubmit((values)=>{mySubmitHandler(values, this.props);}

2 Define onSubmit in mergeProps instead of mapDispatchToProps, so it has access to stateProps. Dan Abramov recommends against using mergeProps (for performance reasons), so this seems suboptimal.

  function mergeProps(stateProps, dispatchProps, ownProps) {
  const { user } = stateProps.authReducer;
  const { dispatch } = dispatchProps;
  return {
    ...ownProps,
    ...dispatchProps,
    ...stateProps,
     onSubmit: (values) => {
       const { name, description } = values;
       const thing = new Thing(name, description, []);
       dispatch(createNewThing(thing, user.id));
     }
  }

3 Copy the state properties into redux-form fields which are not mapped to any input controls in the form component. This will ensure they are accessible in the values parameter passed back to handleSubmit. This seems like kind of a hack.

There's got to be a better way!

Is there a better way?

stone
  • 8,422
  • 5
  • 54
  • 66

4 Answers4

18

After spending time refining this question, option #1 is actually pretty good, in the final iteration (arrow function that passes all props back to the custom handler). It allows the component to be stateless and completely ignorant of any state that it doesn't consume. So I'm going to call that a reasonable answer. I would love to hear your better solutions!


Define a submit handler using an arrow function in the form component, and from there call my own custom submit handler function passed in from mapDispatchToProps.

<form onSubmit={ handleSubmit((values)=>{mySubmitHandler(values, this.props.user);}

Then to make this handler completely agnostic, pass the entire this.props back to the custom handler:

<form onSubmit={ handleSubmit((values)=>{mySubmitHandler(values, this.props);}

If the Submit function only needs the values and the props that weren't part of the form, we can pass back just those props which the form doesn't use. In a stateless component, this might look like:

const Thing_Create = ({ fields: {name, description}, 
    error, 
    handleSubmit, 
    mySubmitHandler, 
    submitting, 
    onCancel, 
    ...otherprops}) => {
return (
<div>
  <form onSubmit={ handleSubmit((values)=>{
    mySubmitHandler(values, otherprops);}) }>
[rest of form definition would go here]
stone
  • 8,422
  • 5
  • 54
  • 66
  • is it still the best way to do..? – hhsadiq Feb 23 '17 at 16:29
  • good Q and A, it's like you read my mind. I wonder if there is a specific name for this kind of technique? is it an example of currying? you are essentially wrapping your binary function `mySubmitHandler` in another unary function ie, `fn (a) { fn(a, b) }`, which I think technically fits the definition. just curious – sbeam Apr 18 '17 at 20:52
  • @sbeam This isn't currying, but it's close. Currying would be declaring a function `const add = x => y => x + y` whereas this is more like `const add = x => myAdder(x, y)`. I provided a more detailed answer below. – markovchain Aug 24 '19 at 03:01
2

The best way I found is pretty similar to the first solution you came up with.

Take advantage of the fact that the handleSubmit prop passed by redux-form can take a function that will be used as the onSubmit prop, and use partial application to pass any other parameters you need to onSubmit.

Action creator:

function updateUser(id, { name, lastname }) { ... }

Suppose the component gets an onUpdateUser property that just passes the parameters straight to the action creator. And the component also gets user, an object with the id property that we want to pass to the action creator along with the values in the fields.

Component

<form onSubmit={this.props.handleSubmit(this.props.onUpdateUser.bind(null, this.props.user.id))}>

This can easily be re-written for stateless functional components, can work for any amount of parameters as long as you place them on the left in the action creator, and it doesn't need any heavy lifting, just bind

Marco Scabbiolo
  • 7,203
  • 3
  • 38
  • 46
1

I was able to solve this issue by binding this.

Somewhere in render()

<MyCustomFormComponent onSubmit={handleSubmit.bind(this)}/>

The handleSumit

  handleSubmit(fields) {
    this.props.onSubmit(fields); //this is properly bound, HURRAY :-)
  }

MapDispatchToProps for good developers :-)

const mapDispatchToProps = dispatch => ({
  onSubmit: (fields) =>
    dispatch({type: auth.actionTypes.LOGIN, payload: auth.api.login(fields)})
});
hhsadiq
  • 2,871
  • 1
  • 25
  • 39
  • This question was about accessing store state in mapDispatchToProps(). It looks like your example is just showing the "vanilla" case of how to use handleSubmit, where you just need to access data from the fields (without any additional data from the store). For that you can just follow the directions in the redux-form docs. – stone Feb 24 '17 at 08:11
  • Thanks for reply. Actually when I have access to thisOrg in handleSubmit, I can access store state in it, and can pass anything to MapDispatchToProps's onSubmit method. I have checked to get any state value (not just fields), and it was success. Sorry if I missed something or misunderstood your question. I am open to change/delete my answer, if its wrong. – hhsadiq Feb 24 '17 at 11:49
  • Well, the question is about whether it's possible to get store data in `mapDispatchToProps` **without passing it into the form component**, because it's data that the form doesn't need and has no business knowing. I can't tell without seeing your code, but it sounds like you are describing passing data into the form (maybe via `mapStateToProps`? you didn't say) and then passing it back out again in `handleSubmit`. Doing it that way is already covered in the accepted answer. If you're doing something different, you could post your code, if it adds info that hasn't been covered. – stone Feb 24 '17 at 23:54
1

This is a variation of @stone's answer, but you can break down option 1 via functional programming:

const mySubmitHandler = props => values => { /* ...code here */ }

<form onSubmit={ handleSubmit(mySubmitHandler(this.props)) }>

In this case, mySubmitHandler will return a function taking only one argument that has already closed over this.props, so there's no need to explicitly mention the values argument that handleSubmit is passing.

You could perform the currying of mySubmitHandler in a better and more readable way through ramda.

import { curry } from 'ramda';

const mySubmitHandler = curry((props, values) => { /* ...code here */ });

You can go one step further by composing handleSubmit and mySubmitHandler

import { compose, curry } from 'ramda';

const handleSubmit = values => { /* ...code here */ };
const mySubmitHandler = curry((props, values) => { /* ...code here */ });
const onSubmit = compose(handleSubmit, mySubmitHandler);

<form onSubmit={ onSubmit(this.props) }>

Note that onSubmit(this.props) returns a function that takes one argument (values) and has closed over this.props, creating a function that has access to all the properties it needs!

markovchain
  • 515
  • 1
  • 10
  • 25