Creating side effects with the onChange callback in Redux Form (TypeScript included!)
Tags: reduxform, typescript, react, redux
Published: 2020-10-16 19:44:42

Redux Form is a common way to handle forms in React. But as with any large, complicated library, there are always a couple of features that may not seem obvious at first. In this article, we will take a look at how to use the onChange callback in the `reduxForm({})` config.

##The use case
**This function is useful when you want to introduce some side effects to your forms.** This side effect could be anything: maybe you want to fetch data for a dropdown. Or maybe you just want some values to change automatically.

In my case, I was developing a form for granting participation credits to students for showing up to experiments. Say you’re a professor: a student shows up to your experiment, so you mark him as “participated” and grant him, for example, 5 credits for his performance.

![Student participated](https://dev-to-uploads.s3.amazonaws.com/i/1gi033gioitf48xgy3pl.png)<figcaption>A simple credit granting form. You’re a professor running an experiment, and your university’s budget department demands that you submit a form for each student saying how they participated, and how many credits they should get.</figcaption>

Here’s where the `onChange` comes in: another student didn’t show up, with neither an excuse nor a warning. So you mark him as a “no-show”. If a student is a no-show, you probably want him to get zero credits, right? 

Now, wouldn’t it be nice if your Redux Form automatically chose zero credits for the student if you deemed them a “no-show”? That certainly sounds like better UX to me. But beyond UX, you may very well need such functionality in a case like this, where granting credit to a non-participant doesn’t make any sense, and probably breaks the rules of your university’s budget allocation.

![Student no-show](https://dev-to-uploads.s3.amazonaws.com/i/m52xa9z0fya0gud1jkmq.png)<figcaption>Wouldn’t it be nice if selecting the big red X category automatically set the credits to zero?</figcaption>

Of course, you could just add some logic in your component, or in your Redux action, or in your backend. But it’s probably more convenient to have a simple solution that immediately solves the need for visual logic, Redux state logic, and the eventual form submission logic as well.

Enter the `onChange` callback: a simple solution to immediately handle the change according to your rules, leaving the rest of your component, reducers and backend none the wiser that a participant could’ve theoretically been in the big red X category and still gotten some points.

##How to implement
Let’s say our form component looks like this:

```JSX
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
// etc. sub-component imports

class ParticipantModal extends Component {
  render() {
    return (
      <Container>
        <Header />
        <ScrollView>
          <CreditStatusButtons />
          <CreditCount />
        </ScrollView>
        <SaveButton />
      </Container>
    )
  }
}

const ParticipantForm = reduxForm({
  form: "participantForm",
})(ParticipantModal)

export default ParticipantForm;
```

We start out with a simple modal with some sub components (the code for which is not important for this illustration). The way Redux Form works is that our `ParticipantModal` component automatically gets access to an auto-generated object called “form”, which lives in our Redux state and includes pretty much everything needed to make a nice, reactive form. Redux Form takes care of most things, but for our “set credits to zero automatically” we will need to write a little custom logic.

We start out by writing out what we actually want to happen and when. So we write a `handleOnFormChange` function. Let’s say that we have two variables in this form: the `studentStatus` (participated, excused, no-show, etc.) and the `creditValue` (the number of credits to grant).

We can **start out by adding an “onChange” field into our reduxForm** config on the bottom. Then we **declare a function that we want to be called when a form value (any value) changes**. The config will automatically pass in four variables into this function, all of which we will need: `newValues`, `dispatch`, `props` and `previousValues` (in that order!).

```JSX
const handleOnFormChange = (newValues, dispatch, props, previousValues) => {
  const { studentStatus: newStudentStatus } = newValues;
  const {
    studentStatus: prevStudentStatus,
    creditValue: prevCreditValue,
  } = previousValues;
  const { change: changeField } = props;

  /*
    if the user sets the participant as a "no show",
    then their credit value should be automatically set to zero
  */
  if (
    newStudentStatus !== prevStudentStatus && // to prevent dispatching every time
    newStudentStatus === 'noShow' &&
    prevCreditValue > 0
  ) {
    dispatch(changeField('creditValue', 0));
  }
};

const ParticipantForm = reduxForm({
  form: 'participantForm',
  onChange: handleOnFormChange, // <-- add this
})(ParticipantModal);
```

`newValues` and `previousValues` are self-explanatory: they are the form values that were stored in the Redux state before and after the user changed something. `Dispatch` is the Redux dispatch function that is used with every Redux action & reducer. And `props` are the properties that reduxForm passes into your component, from which we destructure the `change` function. (I also rename it to `changeField`, to make it more obvious.) This function takes in the name of the value that we want to change (in our case, the `creditValue`), and the new value we want to set (zero). Make sure to check previous values so the dispatch is called only when you change the status!

And just like that, we’re done! With this little bit of logic, we have achieved the functionality that we wanted.

##Adding TypeScript
This particular project required TypeScript. Though lately I’ve been becoming more and more of a TypeScript fan, one thing I never liked was spending a lot of time looking for interfaces / types for 3rd party libraries.

Well I’ve got you covered. Simply **copy paste the type imports & uses below**, and your linter should get rid of quite a few red lines. You’re going to need `Dispatch` from ‘react’ and `DecoratedFormProps` from ‘redux-form’. 

```typescript
import React, { Component, Dispatch } from 'react';
import { reduxForm, DecoratedFormProps } from 'redux-form';
```

You’ll also need to declare your own interface for the values in your form.

```typescript
interface YourForm {
  studentStatus: string;
  creditValue: number;
}
```

Add them to your “handleOnFormChange” function.

With that, **our final result** should look something like this:

```TSX
import React, { Component, Dispatch } from 'react';
import { reduxForm, DecoratedFormProps } from 'redux-form';
// etc. other component imports

interface YourForm {
  studentStatus: string;
  creditValue: number;
}

class ParticipantModal extends Component {
  render() {
    return (
      <Container>
        <Header />
        <ScrollView>
          <CreditStatusButtons />
          <CreditCount />
        </ScrollView>
        <SaveButton />
      </Container>
    );
  }
}

const handleOnFormChange = (
  newValues: YourForm,
  dispatch: Dispatch<any>,
  props: DecoratedFormProps<YourForm, {}, string>,
  previousValues: YourForm
) => {
  const { studentStatus: newStudentStatus } = newValues;
  const {
    studentStatus: prevStudentStatus,
    creditValue: prevCreditValue,
  } = previousValues;
  const { change: changeField } = props;

  /*
    if the user sets the participant as a "no show",
    then their credit value should be automatically set to zero
  */
  if (
    newStudentStatus !== prevStudentStatus && // to prevent dispatching every time
    newStudentStatus === 'noShow' &&
    prevCreditValue > 0
  ) {
    dispatch(changeField('creditValue', 0));
  }
};

const ParticipantForm = reduxForm({
  form: 'participantForm',
  onChange: handleOnFormChange,
})(ParticipantModal);

export default ParticipantForm;
```

And that’s it! 🙂

##P.S.: “But I’m still getting a TypeScript linter error?”
At the end of this, you may see a linter error at the bottom. Specifically, the `ParticipantModal` component that gets passed into reduxForm will have a warning that says something like `Argument of type 'typeof ParticipantModal' is not assignable to parameter of type 'ComponentType<InjectedFormProps<FormValues, {}, string>>'`.

I’ll be honest, I have no idea how to fix this linter error. I’ve tried and I’ve Googled it a dozen times, to no avail. I’ve just accepted that all my Redux Form components will have one TypeScript error on the bottom.

If you find a solution, would you please kindly share it with me? As a token of my gratitude, I promise I’ll send you the best picture of a cookie I can find 😇

Thanks for reading!

## Edit on 2 December 2020: TypeScript linter error solved
Here's how to solve that TypeScript error above. Change the following lines as follows:

```TSX
// import { reduxForm, DecoratedFormProps } from 'redux-form'; // old
import { reduxForm, DecoratedFormProps, InjectedFormProps } from 'redux-form';

// ...

// class ParticipantModal extends Component { // old
class ParticipantModal extends Component<Props, InjectedFormValues<YourForm, Props>> {
// Props is the interface with the normal, non-form-related props of this component 

// ...

  // props: DecoratedFormProps<YourForm, {}, string>, // old
  props: DecoratedFormProps<YourForm, Props, string>, 

// ...

// const ParticipantForm = reduxForm({ // old
const ParticipantForm = reduxForm<YourForm, Props>({ 


```

Other lines shouldn't need any changes.

If your component has no Props, you can replace the "Props" interface with an empty object, e.g. `class ParticipantModal extends Component<{}, InjectedFormValues<YourForm, {}>>`.

Thanks again for reading, and good luck with your forms!