Fetching, Fetched, and Fetch Error is Not Enough.
Tags: javascript, react, webdev, node
Published: 2019-06-05 05:51:26

When we read about making HTTP requests we often see the usual _fetching_, _fetched_, or a _fetch error_ state. And then the user interface should be updated to reflect that update. These three states describe the most important implementations to doing any CRUD (Create, Read, Update, Delete) operation.

As developers, we are responsible for keeping the user engaged with our interface and help them have the best experience possible. We think of users as our allies and ~~unwanted~~ bugs as our enemies.

When retrieving data for example, we want to let the user _know_ that we are trying to retrieve data while they are waiting for it. When the data is retrieved, we should show the data. When an error occurred and the data was not able to be retrieved for what ever the reason says in the error object, we must let the user know that there was an error and utilize what was retrieved in the error. The last thing we want to do is leave them hanging--unless we're trying to get them to leave and never come back of course.

_But that's not enough_.

There's a fourth state that should not only belong with the fetching states, but in _any_ sort of operation, especially CRUD operations.

![thinking-fetching-fourth-state-in-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/fetching-fetched-fetcherror-is-not-enough/thinking.jpg)

## Timed Out

At the company I work at, we do a lot of HTTP requests. One of the biggest problems we had was that there were random _frozen_ loading spinners in random web pages that ended up being stuck in the phase until the user refreshed the page. This doesn't happen all the time however. But my boss _really did not_ like frozen loading spinners. This was extremely bad user experience. I don't blame him, because every user affected by this issue is left hanging and forced to do some action that is _totally opposite of what we want them to do_.

Can you guess what it is? Yes, you guessed right. They hit the back button and go somewhere else. They close their browser and occupy themselves with something else. The list goes on. Or the worst thing that could ever happen... is that they hit the back button and decide to use a competitor's website instead. _We just lost a potential valuable customer_. Bad user experience is an implicit loss of money, unfortunately :(.

_You need a timed out state_. When the server doesn't respond or for some reason the fetching state was dispatched and the call got stuck right before sending the request (it happens), the loading spinner you attached the _fetching_ state to becomes frozen. It's no longer a temporary loading spinner used to signal that the data is coming. It's now a loading spinner that runs infinitely and the whole world is ~~never~~ coming to an end. You've now passed the responsibility of handling that bug to the user. You failed as a developer. Please try again.

So how do we implement this in React?

Some libraries like [axios](https://github.com/axios/axios) provide a _timeout_ option. But you shouldn't rely on this to be 100% accurate. My experience at the company I work at has shown me that it is not enough and we should not heavily depend on it.

Instead of doing the usual fetching/fetched/fetch error implementation we'll go ahead and do an updating/updated/update error one because we hear "fetch" on every corner of the street in JavaScript.

For this tutorial we will make a custom [react hook](https://reactjs.org/docs/hooks-intro.html) that will provide a method `updateUser` to invoke the update handler, and inside it will dispatch some actions while making the API call. It will also set a temporary timeout function to be invoked after \_\_\_ amount of seconds.

The hook will be registered with a few states. These states along with the update handler will be passed to the caller. We'll start with the hook implementation and then apply the timeout part afterwards.

Lets start with a basic component `App.js` and work our way up:

> App.js

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

const App = (props) => {
  return <div>Update Timeout</div>
}

export default App
```

Now to go ahead and start with the whole "updating user" implementation we are going to create a hook called `useUpdateUser`. The main point of this hook is to perform an update operation on a user's _email_ or _password_.

> useUpdateTimeout.js

```jsx
import axios from 'axios'

const useUpdateUser = () => {
  const updateUser = async (userId, params) => {
    try {
      if (!userId) {
        throw new Error('userId is undefined')
      } else if (!params) {
        throw new Error('params is undefined')
      }
      const url = `https://someapi.com/v1/account/${userId}/`
      const response = await axios.put(url, params)
      const updatedUser = response.data
      return updatedUser
    } catch (error) {
      throw error
    }
  }

  return {
    updateUser,
  }
}

export default useUpdateUser
```

Now to define the states we are going to use [_useReducer_](https://reactjs.org/docs/hooks-reference.html#usereducer). I personally use useReducer on just about every hook that uses some sort of state (even when its just 1 state -\_-).

```jsx
const initialState = {
  updating: false,
  updated: false,
  updateError: null,
}
```

Here we defined three necessary states to make an app running normally. In the JavaScript community, we've often been taught that when there are no errors in a request, you pass in _null_ to the error argument so that the caller will know that data has been retrieved without issues. So, we used the same standard here on _updateError_ because it works fine here too.

Now we need to define a reducer to apply changes to concurrent state updates. The reducers should reflect on the initial state:

```jsx
import { useReducer } from 'react'
```

And then after the _initialState_ implementation we would define the reducer:

```jsx
const reducer = (state, action) => {
  switch (action.type) {
    case 'updating':
      return { ...initialState, updating: true }
    case 'updated':
      return { ...initialState, updated: true }
    case 'set-error':
      return { ...initialState, updateError: action.error }
    default:
      return state
  }
}
```

You might have noticed that the _initialState_ is being spread in each switch case instead of spreading the usual _state_. Why is that?

This effectively does the _same thing_ as you would normally write with spreading _state_, only now we don't have to write all the boilerplate code. To avoid unecessary bugs and code size, we want the whole implementation to be as _simple_ as possible. When _updating_ switches to _true_, the UI should be set back to its original state and _only_ care about the _updating_ part. When the update has finished and the user profile has been updated, the UI should be set back to its original state and also _only_ care that the user profile was updated (the _updated_ part of the state). The same also goes for _updateError_.

Otherwise we'd be writing it like this:

```jsx
const reducer = (state, action) => {
  switch (action.type) {
    case 'updating':
      return { ...state, updated: false, updating: true }
    case 'updated':
      return { ...state, updated: true, updating: false, updateError: null }
    case 'set-error':
      return {
        ...state,
        updated: false,
        updating: false,
        updateError: action.error,
      }
    default:
      return state
  }
}
```

Which version do you prefer? I don't know about you but I prefer the _initialState_ version! (One would argue that using the initialState version takes away all the power and flexibility of our state updates. I totally agree, but the states here are accomplishing the same goal).

The next thing we want to do now is to attach our implementation to our `useUpdateTimeout` hook with _useReducer_:

> useUpdateTimeout.js

```jsx
const useUpdateUser = () => {
  const [state, dispatch] = useReducer(reducer, initialState)

  const updateUser = async (userId, params) => {
    try {
      if (!userId) {
        throw new Error('userId is undefined')
      } else if (!params) {
        throw new Error('params is undefined')
      }
      const url = `https://someapi.com/v1/account/${userId}/`
      const response = await axios.put(url, params)
      const updatedUser = response.data
      return updatedUser
    } catch (error) {
      throw error
    }
  }

  return {
    updateUser,
  }
}
```

And we also want to provide these helpful utilities to the caller by spreading them on the return statement so that they actually update the components when the states change:

```jsx
return {
  ...state,
  updateUser,
}
```

So far, we now have something like this:

> useUpdateTimeout.js

```jsx
import { useReducer } from 'react'
import axios from 'axios'

const initialState = {
  updating: false,
  updated: false,
  updateError: null,
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'updating':
      return { ...initialState, updating: true }
    case 'updated':
      return { ...initialState, updated: true }
    case 'set-error':
      return { ...initialState, updateError: action.error }
    default:
      return state
  }
}

const useUpdateUser = () => {
  const [state, dispatch] = useReducer(reducer, initialState)

  const updateUser = async (userId, params) => {
    try {
      if (!userId) {
        throw new Error('userId is undefined')
      } else if (!params) {
        throw new Error('params is undefined')
      }
      const url = `https://someapi.com/v1/account/${userId}/`
      const response = await axios.put(url, params)
      const updatedUser = response.data
      return updatedUser
    } catch (error) {
      throw error
    }
  }

  return {
    ...state,
    updateUser,
  }
}

export default useUpdateUser
```

When we make the app invoke _updateUser_ it's a good idea to also make it dispatch some actions while going through its process for the components to update accordingly:

```jsx
const updateUser = async (userId, params) => {
  try {
    dispatch({ type: 'updating' })
    if (!userId) {
      throw new Error('userId is undefined')
    } else if (!params) {
      throw new Error('params is undefined')
    }
    const url = `https://someapi.com/v1/api/user/${userId}/`
    const response = await axios.put(url, params)
    const updatedUser = response.data
    dispatch({ type: 'updated' })
    return updatedUser
  } catch (error) {
    dispatch({ type: 'set-error', error })
  }
}
```

The UI should change depending on which type of action is being dispatched at the time.

The app should be running just fine right now and we can just stop there. However, this post was to implement a _timedOut_ state, so we are going to implement that next.

To begin with, we should think about the _setTimeout_ function that JavaScript already provides us. This will help make a timed out request happen because it can be used to dispatch a _timed-out_ action that the UI components can listen from.

When the _timed-out_ action is dispatched, the UI should immediately just drop what it was doing and display in their space that the operation was timed out. This way, the user will know that either something happened with their internet or something went wrong with the server. You can optionally provide a _retry_ button to retry the request. I'll be making another tutorial to implement that so hang on there if you're looking for some guidance!

Anywho, the very first thing we want to declare is _where_ to attach the setTimeout reference to.

For this, we'll import _[useRef](https://reactjs.org/docs/hooks-reference.html#useref)_ from react and attach it onto the _.current_ property inside the useEffect block:

```jsx
import { useReducer, useRef } from 'react'
```

Putting it inside the hook:

```jsx
const [state, dispatch] = useReducer(reducer, initialState)
const timeoutRef = useRef(null)
```

And now, inside the _updateUser_ method this is where we declare the _setTimeout_ function to start counting down to dispatch a _timed-out_ action if the timer ever reaches the ends of its life:

```jsx
const updateUser = async (userId, params) => {
  try {
    dispatch({ type: 'updating' })
    if (!userId) {
      throw new Error('userId is undefined')
    } else if (!params) {
      throw new Error('params is undefined')
    }
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current)
    }
    timeoutRef.current = setTimeout(() => {
      dispatch({ type: 'timed-out' })
    }, 30000)
    const url = `https://someapi.com/v1/api/user/${userId}/`
    const response = await axios.put(url, params)
    clearTimeout(timeoutRef.current)
    const updatedUser = response.data
    dispatch({ type: 'updated' })
    return updatedUser
  } catch (error) {
    clearTimeout(timeoutRef.current)
    dispatch({ type: 'set-error', error })
  }
}
```

Final output:

```jsx
import { useReducer, useRef } from 'react'
import axios from 'axios'

const initialState = {
  updating: false,
  updated: false,
  updateError: null,
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'updating':
      return { ...initialState, updating: true }
    case 'updated':
      return { ...initialState, updated: true }
    case 'set-error':
      return { ...initialState, updateError: action.error }
    case 'timed-out':
      return { ...initialState, timedOut: true }
    default:
      return state
  }
}

const useUpdateUser = () => {
  const [state, dispatch] = useReducer(reducer, initialState)
  const timeoutRef = useRef(null)

  const updateUser = async (userId, params) => {
    try {
      dispatch({ type: 'updating' })
      if (!userId) {
        throw new Error('userId is undefined')
      } else if (!params) {
        throw new Error('params is undefined')
      }
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current)
      }
      timeoutRef.current = setTimeout(() => {
        dispatch({ type: 'timed-out' })
      }, 30000)
      const url = `https://someapi.com/v1/api/user/${userId}/`
      const response = await axios.put(url, params)
      clearTimeout(timeoutRef.current)
      const updatedUser = response.data
      dispatch({ type: 'updated' })
      return updatedUser
    } catch (error) {
      clearTimeout(timeoutRef.current)
      dispatch({ type: 'set-error', error })
    }
  }

  return {
    ...state,
    updateUser,
  }
}

export default useUpdateUser
```

This actually looks like a finished implementation so far! However, I like to provide a little customization to the hook just to make it more flexible by letting the caller provide a custom timeout:

```jsx
const useUpdateUser = ({ timeout = 30000 }) => {
  const [state, dispatch] = useReducer(reducer, initialState)
  const timeoutRef = useRef(null)

  ...
}
```

```jsx
timeoutRef.current = setTimeout(() => {
  dispatch({ type: 'timed-out' })
}, timeout)
```

What's going to happen with _updateUser_ is that it first dispatches an _updating_ action. The UI components should display some kind of "pending" representation so that the user gets excited about their profile being updated. If this method ever gets accidentally called _twice_, we have an early _clearTimeout(timeoutRef.current)_ happening right above the _setTimeout_ line so that it can remove the previous one that was just set.

The line after that is the _setTimeout_ line. This is the most important part of this entire post, as without it there won't be no timeout feature! :)

Once the _await_ call succeeds, we know the user's profile was updated successfully. Once that success response arrives, we then know that the _setTimeout_ statement isn't needed anymore, so we erase it with _clearTimeout(timeoutRef.current)_. And finally at the end of the execution we dispatch an _updated_ action so that the successful profile update can be reflected in the interface.

If there were any errors during the update process, a _clearTimeout(timeoutRef.current)_ also runs. The reason is because since we actually received some response back from the request, the timeout is no longer relative in the path that the code is going because now we only care about the error that occurred instead.

## The User Interface

This is one way the hook would be implemented with this hook in a real scenario:

```jsx
import React, { useState } from 'react'
import './App.css'
import useUpdateUser from './useUpdateUser'

const App = (props) => {
  const {
    updating,
    updated,
    updateError,
    timedOut,
    updateUser,
  } = useUpdateUser({
    timeout: 12000,
  })

  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const onSubmit = (e) => {
    e.preventDefault()
    const params = { email, password }
    updateUser('my-user-id123', params)
  }

  const isInitial = !updating && !updated && !updateError && !timedOut

  const errMsg =
    updateError &&
    (updateError.message || 'An error occurred. Please try again later')

  return (
    <div className='container'>
      <h2>
        {isInitial && 'Update your email or password below'}
        {updating && 'Updating your profile...'}
        {updated && 'Your profile has been updated'}
        {errMsg && <span className='error-txt'>{errMsg}</span>}
        {timedOut &&
          'We did not receive a response from the server. Please try again later'}
      </h2>
      <form onSubmit={onSubmit}>
        <div>
          <input
            type='text'
            placeholder='Email'
            name='email'
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </div>
        <div>
          <input
            type='text'
            placeholder='Password'
            name='password'
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>
        <div>
          <button type='submit'>Submit</button>
        </div>
      </form>
    </div>
  )
}

export default App
```

Here are the most beautiful screenshots of the implementation:

### Initial UI:

![initial-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/fetching-fetched-fetcherror-is-not-enough/1.JPG)

### Updating

![updating-javascript-fetching](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/fetching-fetched-fetcherror-is-not-enough/3.JPG)

### Updated

![updated-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/fetching-fetched-fetcherror-is-not-enough/2.JPG)

### Update Error

![error-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/fetching-fetched-fetcherror-is-not-enough/4.JPG)

### Update Timed Out

![timeout-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/fetching-fetched-fetcherror-is-not-enough/5.JPG)

## Conclusion

There we have it! Stay tuned for another tutorial for next time. Also, you can subscribe to my newsletter at https://jsmanifest.com to get my updates straight to your inbox. They're _free_.