DEV Community

Cover image for Creating a TimeoutMessage Component for Temporary User Messages in React
Radzion Chachura
Radzion Chachura

Posted on • Edited on • Originally published at radzion.com

Creating a TimeoutMessage Component for Temporary User Messages in React

Watch on YouTube | 🐙 GitHub | 🎮 Demo

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

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.

<TimeoutMessage
  deps={[isAnonymous, name, country]}
  timeout={5000}
  render={() => <UpdateProfileMessage />}
/>
Enter fullscreen mode Exit fullscreen mode

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.

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
}
Enter fullscreen mode Exit fullscreen mode

useTimeoutState Hook

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

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
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)