Synchronous State With React Hooks
Tags: hooks, react, javascript, functional
Published: 2020-12-22 10:48:05

[**NOTE:** Since I wrote this article, I've turned this code into an NPM package that can be found here: https://www.npmjs.com/package/@toolz/use-synchronous-state]

Since I've converted my dev to React Hooks (rather than class-based components), I keep running head-first into the asynchronous nature of `state` updates.  I don't honestly understand why this rarely seemed like a problem in my class-based components.  But with functions/Hooks, I keep hitting this "roadblock".  And unlike other articles I've written, this isn't my cocky declaration that I have solved **_ALL THE THINGS!!!_**  Rather, I'd be thrilled if someone can show me an improvement on my proposed solution.
<br/><br/>


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6b55iq39bjvoqoth6vu3.png)
## The Problem
We have a complex form.  There are field-level validations.  And form-level validations.  And some of those validations vary based on the values entered into other fields.   

Because the user's path through the form is not always linear, the logic in the component is broken up, as much as possible, into small manageable functions.  So for example, when you update the `Member ID` field, it calls `updateMemberId()`.  Then it calls `validateMemberId()`, to see if we should show any error messages to the user.  Then it calls `validateForm()`, to see if we should be checking all of the other fields on the form.

So the code ends up looking something like this:

```javascript
export default function App() {
  const [memberId, setMemberId] = useState('');
  const [validateEntireForm, setValidateEntireForm] = useState(false);

  const updateMemberId = userValue => {
    setMemberId(userValue);
    validateMemberId();
    if (validateEntireForm)
      validateForm();
  }

  const validateForm = () => {
    if (!validateEntireForm)
      setValidateEntireForm(true);
    validateMemberId();
    // validate the rest of the fields in the form  
  }

  const validateMemberId = () => {
    // validate based on the CURRENT value of 'memberId'
    return validOrNot;
  }

  return (<>UX Here...</>);
}
```

I won't ask you to mentally "load" this pseudo-code.  So I'll just tell you the problem that I run into:  Tabbing out of the `memberId` field triggers `updateMemberId()`, which in turn updates the _state_ value of `memberId`, which then leads to calling `validateMemberId()`.  Inside `validateMemberId()`, we'll be referencing the _state_ value for `memberId` - the value that was set microseconds previously inside `updateMemberId()`.

Of course, even though the value of the `memberId` state variable was updated during `updateMemberId()`, what happens when `validateMemberId()` tries to reference that same variable?  That's right, it doesn't see the _latest_ value of `memberId`.  In fact, it sees whatever was saved into `memberId` during the _previous_ update.  So `validateMemberId()` is always one update _behind_.  

Of course, this problem is only exacerbated if we've flipped the `validateEntireForm` flag.  Because once `validateForm()` gets called, it will also lead to referencing the value of `memberId` - which will still be stuck on the _previous_ value.

The "problem" is pretty simple - and one that has been inherent in React since it was created.  State updates are _asynchronous_.  This was true in class-based components.  It's true with functions/Hooks.  But for whatever reason, I've only recently been running into ever-more headaches from this basic fact.  

Since `setMemberId()` is asynchronous, subsequent references to `memberId` don't reflect the most up-to-date value that was just entered by the user.  They reference the _previous_ value.  And that obviously throws off the validation.
<br/><br/>


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/opfvv9mdpx94fvs17krl.png)
## Standard (Poor) Solutions
There are several "standard" ways to address this problem.  In some situations, they might be appropriate.  But in most scenarios, I really don't care for them at all.  They include:
<br/>

1. **Consolidate all these functions into _one_ routine.**  If it's all one function, then we can set one temp variable for the new field value, then use that same temp variable to update the field's state variable, and to check for field-level validity, and to check for global form validity.  But if the "solution" is to stop creating small, targeted, single-use functions, well then... I don't really want to pursue that "solution" at all.<br/>

1. **Explicitly pass the values into each function.**  For example, `updateMemberId()` could grab the newly-entered value and pass it _into_ `validateMemberId()`.  But I don't like that.  Why???  Well, because in this example, the state variable is the _system of record_.  In other words, I don't want `validateMemberId()` to only validate whatever value was blindly passed into it.  I want that function to validate _the current state value_.  And if that's to occur, the function should always be looking back into _state_ to grab the latest value.  I've also found that, when building complex user interactions, there can sometimes be many different scenarios where a validation needs to be checked.  And during those scenarios, there's not always a convenient variable to pass into the validation function.  During those scenarios, it makes far more sense for the validation function to just grab the state value on its own.<br/>

1. **Use reducers.** I dunno.  Maybe it's because I hate Redux, but I _really_ dislike feeling compelled to convert most of my calls to `useState()` into `useReducer()`.  Once you go down the `useReducer()` path, more and more _and more_ of your logic ends up getting sucked out of your components and into all of these helper functions.  And once it's sitting in all those helper functions, most devs feel compelled to start sorting them off into their own separate card catalog of directories.  Before you know it, your previously-simple component has become an 8-file octopus of confusion.<br/>

1. **Use `useRef()`??**  I've seen several references to this on the interwebs.  Honestly, any time I start following this rabbit hole, I end up burning precious hours and getting no closer to a solution.  If `useRef()` is the answer to this problem, I'd _love_ to see it.  But so far... it seems lacking.<br/>

1. **Use `useEffect()`**  Stop.  No, seriously.  Just... _stahp_.  I've seen several threads on the interwebs suggesting that the "solution" to this quandary is to leverage `useEffect()`.  The idea is that, for example, when we want to update `memberId`, we also create a call to `useEffect()` that handles all of the side effects that happen once we update `memberId`.  But that often threatens to turn the logic of our components on its ear.  It's not uncommon for me to have a component where changing _one_ state value forces me to check on the values of _several other_ state values.  And once you start chunking all of that crap into the dependency array... well, you might as well just start building a whole new tree of Higher Order Components.<br/>

1. **Use the _verbose_ version of the state variable's _set_ function.**  This was the avenue I pursued for a while.  But it can get, well... _ugly_.  Consider this:<br/>

```javascript
  const updateMemberId = async userValue => {
    let latestMemberId;
    await setMemberId(userValue => {
      latestMemberId = userValue;
      return userValue;
    });
    validateMemberId();
    if (validateEntireForm)
      validateForm();
  }
```

This... doesn't really solve much.  On one hand, once we're past the `setMemberId()` logic, we have the latest-greatest value saved in `latestMemberId`.  But we _already_ had that value saved in `userValue` and we'll still need to pass it into all of the downstream functions.  Furthermore, we've started to litter up our logic with `async/await` - which is a problem when we have logic that shouldn't _really_ be asynchronous.
<br/><br/>


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8n6evb95gsglhjrqwuxk.jpg)
## The Problem - Simplified
The "problem" I'm trying to highlight can be distilled down to this basic issue:

```javascript
const someFunction = someValue => {
  setSomeStateVariable(someValue);
  if (someConditionBasedOnSomeStateVariable) {
    //...won't trigger based on the new value of 'someStateVariable'
  }
  callAFollowOnMethod();
}

const callAFollowOnMethod = () => {
  if (someStateVariable)
    //...won't recognize the latest value of 'someStateVariable'
}
```

If we want to distill this into an even _simpler_ example, there are just some times when we really want to do something like this:

```javascript
console.log(stateVariable); // 1
setStateVariable(2);
console.log(stateVariable); // 2
setStateVariable(3);
console.log(stateVariable); // 3
```

In other words, _sometimes_, you really need to update a state variable and know that, very soon thereafter, you can retrieve the _latest_, most _up-to-date_ value, without worrying about asynchronous effects.

To be absolutely clear, I fully understand that _some_ things will always be, and _should_ always be, asynchronous.  For example, if you have three state variables that hold the responses that come back from three consecutive API calls, then _of course_ those values will be set asynchronously.  

But when you have three state variables that are consecutively set with three simple scalar values - well... it can be kinda frustrating when those values aren't available to be read _immediately_.  In other words, if you can do this:

```javascript
let foo = 1;
console.log(foo); // 1
foo = 2; 
console.log(foo); // 2
```

Then it can be somewhat frustrating when you realize that you can't do this:

```javascript
const [foo, setFoo] = useState(1);
console.log(foo); // 1
setFoo(2);
console.log(foo); // 1
```

So... how do we address this???
<br/><br/>


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/e5f7co2xq6nk83t51sw2.jpg)
## Eureka(?)
Here's what I've been working with lately.  It's dead-simple.  No clever solution here.  But it satisfies two of my main concerns:<br/>

1. I want to always have a way to retrieve the _absolute latest_ state value.<br/>

1. I'd really like to have the new state value _returned to me_ after state updates.  This may not seem like that big-of-a-deal - but sometimes, I really wish that the built-in `set()` functions  would simply return the new value to me.  (Of course, they _can't_ simply return the new value, because they're asynchronous.  So all they could return would be a promise.)<br/>

To address these two issues, I created this (super crazy simple) Hook:

```javascript
import { useState } from 'react';

export default function useTrait(initialValue) {
   const [trait, updateTrait] = useState(initialValue);
   
   let current = trait;
   
   const get = () => current;
   
   const set = newValue => {
      current = newValue;
      updateTrait(newValue);
      return current;
   }
   
   return {
      get,
      set,
   }
}
```

[NOTE: I'm not really sold on the name "trait".  I only used it because I felt it was too confusing to call it some version of "state".  And I didn't want to call the Hook `useSynchronousState` because this isn't really synchronous.  It just gives the illusion of synchronicity by employing a second tracking variable.]

This would get used like this:

```javascript
const SomeComponent = () => {
  const counter = useTrait(0);

  const increment = () => {
    console.log('counter =', counter.get()); // 0
    const newValue = counter.set(counter.get() + 1);
    console.log('newValue =', newValue); // 1
    console.log('counter =', counter.get()); // 1
  }

  return (
    <>
      Counter: {counter.get()}
      <br/>
      <button onClick={increment}>Increment</button>
    </>
  );

  return (<>UX Here...</>);
}
```

This is a reasonable impersonation of synchronicity.  By using two variables to track a single state value, we can reflect the change immediately by returning the value of `current`.  And we retain the ability to trigger re-renders because we're still using a traditional state variable inside the Hook.
<br/><br/>


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/99s35c3zmpzo55jac14c.jpeg)
## Downsides
I don't pretend that this little custom Hook addresses all of the issues inherent in setting a state variable - and then immediately trying to retrieve the _latest_ value of that state variable.  Here are a few of the objections I anticipate:
<br/>

1. `useTrait()` doesn't work if the value being saved is returned in a _truly_ asynchronous manner.  For example, if the variable is supposed to hold something that is returned from an API, then you won't be able to simply `set()` the value and then, on the very next line, `get()` the proper value.  This is only meant for variables that you wouldn't normally think of as being "asynchronous" - like when you're doing something dead-simple, such as saving a number or a string.<br/><br/>

1. It will always be at least _somewhat_ inefficient.  For every "trait" that's saved, there are essentially _two_ values being tracked.  In the vast majority of code, trying to fix this "issue" would be a micro-optimization.  But there are certainly _some_ bulky values that should not be chunked into memory twice, merely for the convenience of being able to immediately retrieve the result of `set()` operations.<br/><br/>

1. It's potentially non-idiomatic.  As mentioned above, I'm fully aware that the Children of Redux would almost certainly address this issue with `useReducer()`.  I'm not going to try to argue them off that cliff.  Similarly, the Children of Hooks would probably try to address this with `useEffect()`.  Personally, I _hate_ that approach, but I'm not trying to fight that Holy War here.<br/><br/>

1. I feel like I'm overlooking some simpler solution.  I've done the requisite googling on this.  I've read through a pile of StackOverflow threads.  I haven't _grokked_ any better approach yet.  But this is one of those kinda problems where you just keep thinking that, "I gotta be overlooking some easier way..."