Creating a TimeoutMessage Component for Temporary User Messages in React
Tags: react, timeoutmessage, userexperience, programming
Published: 2023-09-12 03:57:37

✨ [**Watch on YouTube**](https://youtu.be/tUbas1Ni2yo)  |  🐙 [GitHub](https://github.com/radzionc/reactkit/blob/main/app/pages/timeout.tsx)  |  🎮 [Demo](https://reactkit.radzion.com/timeout)

There is a useful UX pattern of displaying a temporary message in response to a user's action. Recently, I had to add such a timeout message to my project and I thought it might be beneficial to package it into an abstract component and share it with you.

## How To Use the TimeoutMessage Component

Here's how you can use it to display a temporary message after a user updates their profile.

![Update Profile Message](https://cdn-images-1.medium.com/proxy/1*J4qnfDJTrXh0gveJ5PpxOA.jpeg)

If one of the values in the `deps` array has changed, the component will call the `render` function and display the message for five seconds.

```tsx
<TimeoutMessage
  deps={[isAnonymous, name, country]}
  timeout={5000}
  render={() => <UpdateProfileMessage />}
/>
```

## TimeoutMessage Component

The `TimeoutMessage` component is a simple wrapper around the `useTimeoutState` and `useEffectOnDependencyChange` hooks. Instead of using those hooks directly, we achieve a more declarative and compact code using the `TimeoutMessage` component.

```tsx
import { DependencyList, ReactNode } from "react"
import { useEffectOnDependencyChange } from "../hooks/useEffectOnDependencyChange"
import { useTimeoutState } from "../hooks/useTimeoutState"

interface TimeoutMessageProps {
  render: () => ReactNode
  timeout: number
  deps: DependencyList
}

export const TimeoutMessage = ({
  render,
  timeout,
  deps,
}: TimeoutMessageProps) => {
  const [shouldShowMessage, setShouldShowMessage] = useTimeoutState(
    false,
    timeout
  )

  useEffectOnDependencyChange(() => {
    setShouldShowMessage(true)
  }, deps)

  return shouldShowMessage ? <>{render()}</> : null
}
```

## useTimeoutState Hook

The `useTimeoutState` hook is similar to the `useState` hook, but it resets the state to the default value after a given timeout.

```tsx
import { useEffect, useState } from "react"

export const useTimeoutState = <T,>(
  defaultValue: T,
  timeoutDuration: number
) => {
  const [state, setState] = useState(defaultValue)

  useEffect(() => {
    if (state !== defaultValue) {
      const timeout = setTimeout(() => {
        setState(defaultValue)
      }, timeoutDuration)

      return () => clearTimeout(timeout)
    }
  }, [state, defaultValue, setState, timeoutDuration])

  return [state, setState] as const
}
```

In our `TimeoutMessage` component, we use `useTimeoutState` to monitor the message visibility. We set `shouldShowMessage` to `true` when the `deps` change, and the `useTimeoutState` hook will reset it back to `false` after the specified timeout.

## useEffectOnDependencyChange Hook

The `useEffectOnDependencyChange` hook is the same as the `useEffect` hook. However, it only calls the `effect` function when one of the values in the `deps` array has changed, and will not call it on the initial render.

```tsx
import { DependencyList, useEffect, useRef } from "react"

export const useEffectOnDependencyChange = (
  effect: () => void,
  deps: DependencyList
) => {
  const prevDeps = useRef(deps)
  useEffect(() => {
    const hasDepsChanged = !prevDeps.current.every((dep, i) => dep === deps[i])
    if (hasDepsChanged) {
      effect()
      prevDeps.current = deps
    }
  }, deps)
}
```