5 Critical Tips for Composing Event Handler Functions in React
Tags: react, javascript, node, webdev
Published: 2020-05-23 14:21:37

Find me on [medium](https://medium.com/@jsmanifest)

JavaScript is praised for its unique ways to compose and create functions. That's because in JavaScript, functions are _first class citizens_ meaning they can be treated as values and have all of the operational properties that others have like being able to be assigned to a variable, passed around as a function argument, returned from a function, etc.

We will be going over 5 critical tips to compose event handlers in react. This post will not cover everything that is possible, but it will cover important ways to compose event handlers that every react developer should know, _minimally_!

We're going to start with an input element and attach a `value` and `onChange` prop to start off:

```jsx
import React from 'react'
import './styles.css'

function MyInput() {
  const [value, setValue] = React.useState('')

  function onChange(e) {
    setValue(e.target.value)
  }

  return (
    <div>
      <input type='text' value={value} onChange={onChange} />
    </div>
  )
}

export default MyInput
```

Our _event handler_ is the `onChange` and the first argument is the event object coming from the element that the handler was attached with.

What can we improve on from here? Well, it's generally a good practice to write components that are reusable, and we _can_ make this reusable.

## 1. Move the setter in a higher level

One way is to pass the responsibility of setting the `value` state up to the `props` so that other components can reuse this input:

```jsx
import React from 'react'
import MyInput from './MyInput'

function App() {
  const [value, setValue] = React.useState('')

  return <MyInput value={value} />
}

export default App
```

That means we would also have to give control over the event handler (which holds the state setter) to the parent:

```jsx
function App() {
  const [value, setValue] = React.useState('')

  function onChange(e) {
    setValue(e.target.value)
  }

  return <MyInput value={value} onChange={onChange} />
}
```

```jsx
function MyInput({ value, onChange }) {
  return (
    <div>
      <input type='text' value={value} onChange={onChange} />
    </div>
  )
}
```

But all we did was move the state and the event handler to the parent and ultimately our `App` component is the exact same as our `MyInput`, only named differently. So what's the point?

## 2. Wrap your event handlers if more information could be needed for extensibility purposes

Things begin to change when we start composing. Take a look at the `MyInput` component. Instead of directly assigning `onChange` to its `input` element, we can instead give this reusable component some additional functionality that make it more useful.

We can manipulate the `onChange` by _composing it inside another onChange_ and attach the new `onChange` onto the element instead. Inside the new `onChange` it will call the original `onChange` from props so that the functionality can still behave normally--as if nothing changed.

Here's an example:

```jsx
function MyInput({ value, onChange: onChangeProp }) {
  function onChange(e) {
    onChangeProp(e)
  }

  return (
    <div>
      <input type='text' value={value} onChange={onChange} />
    </div>
  )
}
```

This brings the awesome ability to inject additional logic when the `value` of the `input` changes. It behaves normally because it still calls the original `onChange` inside its block.

For example, we can now _force_ the input element to accept only number values and only take in a maximum of 6 characters in length, which is useful if you we want to use this for verifying logins through user's phones:

```jsx
function isDigits(value) {
  return /^\d+$/.test(value)
}

function isWithin6(value) {
  return value.length <= 6
}

function MyInput({ value, onChange: onChangeProp }) {
  function onChange(e) {
    if (isDigits(e.target.value) && isWithin6(e.target.value)) {
      onChangeProp(e)
    }
  }

  return (
    <div>
      <input type='text' value={value} onChange={onChange} />
    </div>
  )
}
```

In reality though, this could've all still been implemented in the parent `App` without any problems so far. But what if `onChange` handler in the parent needs _more_ than just the _event_ object from `MyInput`? The `onChange` handler there no longer becomes useful:

```jsx
function App() {
  const [value, setValue] = React.useState('')

  function onChange(e) {
    setValue(e.target.value)
  }

  return <MyInput value={value} onChange={onChange} />
}
```

But what can `App` possibly need other than the event object and knowing that a value of the element is changing, which it is already aware of hence being inside the execution context of the `onChange` handler?

## 3. Take advantage of the original handler that was composed through arguments

Having direct access to the `input` element itself can be extremely helpful. That means its useful to have some `ref` object passed in along with the event object. It's easily done since the `onChange` handler was composed here:

```jsx
function MyInput({ value, onChange: onChangeProp }) {
  function onChange(e) {
    if (isDigits(e.target.value) && isWithin6(e.target.value)) {
      onChangeProp(e)
    }
  }

  return (
    <div>
      <input type='text' value={value} onChange={onChange} />
    </div>
  )
}
```

All we need to do is declare the react hook `useRef`, attach it to the `input` and pass it along inside an object as the _second_ parameter to `onChangeProp` so the caller can access it:

```jsx
function MyInput({ value, onChange: onChangeProp }) {
  const ref = React.useRef()

  function onChange(e) {
    if (isDigits(e.target.value) && isWithin6(e.target.value)) {
      onChangeProp(e, { ref: ref.current })
    }
  }

  return (
    <div>
      <input ref={ref} type='text' value={value} onChange={onChange} />
    </div>
  )
}
```

```jsx
function App() {
  const [value, setValue] = React.useState('')

  function onChange(e, { ref }) {
    setValue(e.target.value)

    if (ref.type === 'file') {
      // It's a file input
    } else if (ref.type === 'text') {
      // Do something
    }
  }

  return (
    <div>
      <MyInput value={value} onChange={onChange} />
    </div>
  )
}
```

## 4. Keep the signature of the higher order function handler and the composed handler identical

It's generally a _very_ important practice to keep the signature of composed functions the same as the original. What I mean is that here in our examples the first parameter of both `onChange` handlers are reserved for the event object.

Keeping the signature identical when composing functions together helps avoid unnecessary errors and confusion.

If we had swapped the positioning of parameters like this:

![swapped parameter positioning in javascript react event handlers](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/5-critical-tips-for-composing-event-handlers-in-react/swapped-parameter-position-in-react-javascript-event-handlers.jpg)

Then it's easily to forget and mess that up when we reuse the component:

```jsx
function App() {
  const [value, setValue] = React.useState('')

  function onChange(e, { ref }) {
    // ERROR --> e is actually the { ref } object so e.target is undefined
    setValue(e.target.value)
  }

  return (
    <div>
      <MyInput value={value} onChange={onChange} />
    </div>
  )
}
```

And it's also less stressful for you and other developers when we avoid this confusion.

A good example is when you want to allow the caller to provide as many event handlers as they want while enabling the app to behave normally:

```jsx
const callAll = (...fns) => (arg) => fns.forEach((fn) => fn && fn(arg))

function MyInput({ value, onChange, onChange2, onChange3 }) {
  return (
    <input
      type='text'
      value={value}
      onChange={callAll(onChange, onChange2, onChang3)}
    />
  )
}
```

If at least one of them attempted to do some method that are specific to strings like `.concat`, an error would occur because the signature is that `function(event, ...args)` and not `function(str, ...args)`:

```jsx
function App() {
  const [value, setValue] = React.useState('')

  function onChange(e, { ref }) {
    console.log(`current state value: ${value}`)
    console.log(`incoming value: ${e.target.value}`)
    setValue(e.target.value)
    console.log(`current state value now: ${value}`)
  }

  function onChange2(e) {
    e.concat(['abc', {}, 500])
  }

  function onChange3(e) {
    console.log(e.target.value)
  }

  return (
    <div>
      <MyInput
        value={value}
        onChange={onChange}
        onChange2={onChange2}
        onChange3={onChange3}
      />
    </div>
  )
}
```

![signature-change-event-handler-javascript-react](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/5-critical-tips-for-composing-event-handlers-in-react/signature-change-event-handler-javascript-react.jpg)

## 5. Avoid referencing and depending on state inside event handlers (Closures)

This is a really dangerous thing to do!

If done right, you should have no problems dealing with state in callback handlers. But if you slip at one point and it introduces silent bugs that are hard to debug, that's when the consequences begin to engulf that extra time out of your day that you wish you could take back.

If you're doing something like this:

```jsx
function onChange(e, { ref }) {
  console.log(`current state value: ${value}`)
  console.log(`incoming value: ${e.target.value}`)
  setValue(e.target.value)
  console.log(`current state value now: ${value}`)
}
```

You should probably revisit these handlers and check if you're actually getting the right results you expect.

If our `input` has a value of `"23"` and we type another `"3"` on the keyboard, here's what the results say:

![event-handler-unsynchronized-states](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/5-critical-tips-for-composing-event-handlers-in-react/event-handler-unsynchronized-states.jpg)

If you understand the execution context in JavaScript this makes no sense because the call to `setValue` had already finished executing before moving to the next line!

Well, that is actually still right. There's nothing that JavaScript is doing that is wrong right now. It's actually _react_ doing its thing.

For a full explanation of the rendering process you can head over to their [documentation](https://reactjs.org/docs).

But, in short, basically at the time whenever react enters a new _render_ phase it takes a _"snapshot"_ of everything that is present _specific to that render phase_. It's a phase where react essentially creates a tree of react elements, which represents the tree _at that point in time_.

By definition the call to `setValue` _does_ cause a re-render, but that render phase is at a future point in time! This is why the state `value` is still `23` after the `setValue` had finished executing because the execution at that point in time _is specific to that render_, sorta like having their own little world that they live in.

This is how the concept of execution context looks like in JavaScript:

![javascript-execution-context-diagram-flow-chart](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/javascript-execution-context-diagram-flow-chart.jpg)

This is react's render phase in our examples (You can think of this as react having their _own execution context_):

![react-render-phase-reconciliation-flow-chart](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/react-render-phase-reconciliation-flow-chart.jpg)

With that said, let's take a look at our call to `setCollapsed` again:

![updating-state-in-render-phase-in-react](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/updating-state-in-render-phase-in-react.jpg)

![updating-state-in-render-phase-in-react2](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/updating-state-in-render-phase-in-react2.jpg)

This is all happening in the same render phase so that is why _collapsed_ is still `true` and `person` is being passed as `null`. When the entire component rerenders then the values in the next render phase will represent the values from the previous:

![react-state-update-reconciliation2](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/this-is-why-you-should-not-depend-on-state-in-callback-handlers/react-state-update-reconciliation2.jpg)

Find me on [medium](https://medium.com/@jsmanifest)