DEV Community

Cover image for Interactive E-Com UI Tutorial: Typing Searchbar
Simon Pfeiffer for Codesphere Inc.

Posted on

Interactive E-Com UI Tutorial: Typing Searchbar

Introduction

In this article, the second in our Interactive E-commerce UI series, we aim to create a dynamic typing search bar using React. We will once again implement this into our existing Shopify Hydrogen store.
This search bar, intended for use in the header of an e-commerce website, will display and animate a series of words, simulating a typing effect.

As an e-commerce store owner implementing this, the main aim for our typing search bar is to inspire users and nudge them towards searching for something they might be looking for.

Typing Searchbar

Image description

Setting the Stage

In the current era of digital commerce, user engagement and interaction are key factors in building a successful online store. One way to increase user engagement is through the use of animations and interactive elements. For every implementation of a new feature or animation, we should aim to change user behavior. Once way to do this is by utilizing human behavior patterns.
For our typing search bar, we're capitalizing on the familiar behavioral pattern of curiosity:

Curiosity
People are curious. They have a desire to experience the unknown and, in particular, to get to know what is hidden. Curiosity leads to trying out new things and thus initiating change.

Just as in the last article, this pattern will be the basis of our design. In a real world scenario where we'd test out this feature to see if an uplift in conversion rate can be achieved, we would use those to set up a hypothesis.

Implementation

We'll be implementing this search bar using React and the Remix framework, both come out of the box with Shopify Hydrogen. Remix provides a productive, integrated development experience and is perfect for building interactive UI components like our typing search bar.
This could also be implemented via vanilla JavaScript and HTML. If you're interested in this, please leave a comment and we'll make sure to create a guide on that too!

Creating the TypingSearchBar Component
Now, with the stage set, let's create a new component for our typing search bar. In this component, we'll use React's state and effect hooks to manage and animate the typing effect.

Import Dependencies and Components
First, import the necessary libraries and components. We'll use Form from Remix for the search functionality and useState and useEffect from React to manage the state and animation logic. In this case, we use the Form component, since we're manipulating the existing search in our Shopify Hydrogen theme. For your project, this might not be necessary. We also import the Input and IconSearch components for the search bar and search icon, which are also part of the Shopify Hydrogen theme.

import { Form } from '@remix-run/react';
import React, { useState, useEffect } from 'react';
import { Input, IconSearch } from '~/components';
Enter fullscreen mode Exit fullscreen mode

Define the TypingSearchBar Component
Next, define the TypingSearchBar component. It receives three props: isHome, params, and variant.These props are once again defined by our Shopify Hydrogen theme and are responsible for context specific styling and translation. In your example you might not need them at all or completely different ones.
We're going to continue with them in our code for this article.

export function TypingSearchBar({
  isHome, // Boolean flag indicating if the component is being used on the home page
  params, // Object containing parameters (e.g., locale)
  variant // Determines the variant of the search bar ('search' or 'minisearch')
}: {
  isHome: boolean;
  params: { locale?: string };
  variant: 'search' | 'minisearch';
}) { ... } // Next Snippet -> Managing the State
Enter fullscreen mode Exit fullscreen mode

Manage the State
Inside the component, we declare several state variables for managing the animation.

// Array of words to display and animate
const words = ['Inspire', 'Search'];

// State variables for managing animation
const [index, setIndex] = useState(0); // Current index of the word array
const [subIndex, setSubIndex] = useState(0); // Current index within the selected word
const [word, setWord] = useState(''); // Current word being built
const [isVisible, setIsVisible] = useState(true); // Flag indicating if the word is visiblequote("Creating the TypingSearchBar Component\n\nWith", "visible; // Flag indicating if the word is visible")

// Timing durations for animation
const displayDuration = 3000; // Duration to display a fully typed word
const idleDuration = 250; // Duration before transitioning to the next word
Enter fullscreen mode Exit fullscreen mode

Implement the Animation
Since we're replacing the placeholder text of a form, the implementation of the animation was a lot more tricky than initially expected. For a simple typing animation, you could use a library like typewriter-effect.

This animation, at its heart, is orchestrated by a useEffect hook. The useEffect hook in React is a function that lets you perform side effects in function components. Here, we use it to manage the sequencing and timing of the typing animation.

Inside the useEffect hook, we've got a few conditionals to manage the different stages of the animation. Let's walk through them:

  • Typing out the word: If subIndex is less than the length of the current word (words[index].length), it means we are still in the process of typing out the word. We then set a timeout of 150 milliseconds before adding the next character to the word and incrementing subIndex by 1. This gives us the effect of each character being typed out one after the other with a slight delay, like a person typing.

  • Waiting once the word is fully typed: If the subIndex is not less than the length of the word, and the isVisible flag is true, it means the word has been fully typed out and is currently visible. We then set a timeout of 3000 milliseconds (3 seconds) before setting the isVisible flag to false. This gives us a pause where the fully typed word is visible for a bit before it starts to disappear.

  • Deleting the word: If the isVisible flag is false and word still has characters in it, it means we are in the process of "deleting" the word. We set a timeout of 100 milliseconds before removing the last character from word. This gives us the effect of the word being deleted one character at a time.

  • Transitioning to the next word: If the isVisible flag is false and the word is empty, it means we've deleted the entire word and are ready to move on to the next word. We set a timeout of 250 milliseconds before resetting word, isVisible, and subIndex to their initial states, and incrementing index by 1 (or resetting it to 0 if it's at the end of the words array). This gives us a brief pause before the next word starts being typed out.

The useEffect hook is set to run every time subIndex, index, word, or isVisible changes. This means every time we change these variables (which we do inside the useEffect hook), the useEffect hook will run again, creating a continuous loop of typing, waiting, deleting, waiting, and so on.

This carefully choreographed sequence of timeouts and state updates is what gives us the typing effect.

useEffect(() => {
  // If the word is still being typed
  if (subIndex < words[index].length) {
    // Add the next character to the word after a delay
    setTimeout(() => {
      setWord(words[index].substring(0, subIndex + 1));
      setSubIndex(prev => prev + 1);
    }, 150);
  }
  // If the word has finished typing, wait before starting to delete it
  else if (isVisible) {
    setTimeout(() => {
      setIsVisible(false);
    }, 1000);
  }
  // If the word has been deleted, wait before starting to type the next word
  else if (subIndex === words[index].length) {
    setTimeout(() => {
      setIsVisible(true);
      setIndex((prev) => (prev + 1) % words.length);
      setSubIndex(0);
    }, 150);
  }
  // If the word is being deleted
  else {
    // Remove the last character from the word after a delay
    setTimeout(() => {
      setWord(words[index].substring(0, subIndex));
      setSubIndex(prev => prev - 1);
    }, 75);
  }
}, [index, subIndex, isVisible]);
Enter fullscreen mode Exit fullscreen mode

Return the Component
The animation effect is implemented within the Form component that houses our search functionality. This is also where we find the props of our TypingSearchBar component being used to alter the action of the form and change the styling of the Input.

The typing animation is not directly tied to user input or form submission, but is purely a visual effect. When users see this animated placeholder text, they can be guided or inspired on what they might want to search for.

The animation effect is achieved by updating the word state variable that is passed as a placeholder to the Input component. The continuous updating of the word variable with the useEffect hook allows us to show the typing, deleting, and word-changing animation within the placeholder of the Input component. The Form component's submission functionality remains unchanged and will trigger a search based on the user's input, not the animated placeholder text.

  return (
    <Form
      method="get"
      action={params.locale ? `/${params.locale}/search` : '/search'}
      className="flex items-center gap-2 w-full"
    >
      <div className="w-full">
        <Input
          className={
            isHome
              ? 'focus:border-contrast/20 dark:focus:border-primary/20'
              : 'focus:border-primary/20'
          }
          type="search"
          variant={variant}
          placeholder={word}
          name="q"
        />
      </div>
      <button
        type="submit"
        className="relative flex items-center justify-center w-8 h-8 focus:ring-primary/5"
      >
        <IconSearch />
      </button>
    </Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

This then leaves us with this code for the full TypingSearchBar Component:

import { Form } from '@remix-run/react';
import React, { useState, useEffect } from 'react';
import { Input, IconSearch } from '~/components';

export function TypingSearchBar({
  isHome,
  params,
  variant
}: {
  isHome: boolean;
  params: { locale?: string };
  variant: 'search' | 'minisearch';
}) {
  const words = ['Inspire', 'Search'];
  const [index, setIndex] = useState(0);
  const [subIndex, setSubIndex] = useState(0);
  const [word, setWord] = useState('');
  const [isVisible, setIsVisible] = useState(true);
  const displayDuration = 3000;
  const idleDuration = 250; // 1 second

  useEffect(() => {
    if (subIndex < words[index].length) {
      const timeout = setTimeout(() => {
        setWord(prevWord => prevWord + words[index][subIndex]);
        setSubIndex(prevSubIndex => prevSubIndex + 1);
      }, 150);
      return () => clearTimeout(timeout);
    } else if (word.length > 0 && isVisible) {
      const timeout = setTimeout(() => {
        setIsVisible(false);
      }, displayDuration);
      return () => clearTimeout(timeout);
    } else if (!isVisible && word.length > 0) {
      const timeout = setTimeout(() => {
        setWord(prevWord => prevWord.slice(0, -1));
      }, 100);
      return () => clearTimeout(timeout);
    } else {
      const timeout = setTimeout(() => {
        setSubIndex(0);
        setWord('');
        setIsVisible(true);
        setIndex(prevIndex => (prevIndex + 1) % words.length);
      }, idleDuration);
      return () => clearTimeout(timeout);
    }
  }, [subIndex, index, word, isVisible]);

  return (
    <Form
      method="get"
      action={params.locale ? `/${params.locale}/search` : '/search'}
      className="flex items-center gap-2 w-full"
    >
      <div className="w-full">
        <Input
          className={
            isHome
              ? 'focus:border-contrast/20 dark:focus:border-primary/20'
              : 'focus:border-primary/20'
          }
          type="search"
          variant={variant}
          placeholder={word}
          name="q"
        />
      </div>
      <button
        type="submit"
        className="relative flex items-center justify-center w-8 h-8 focus:ring-primary/5"
      >
        <IconSearch />
      </button>
    </Form>
  );
Enter fullscreen mode Exit fullscreen mode

We can now simply call this component anywhere we want in our project simply by importing it and passing the specific props.
In our Shopify Hydrogen theme, this is what it would look like:

import {useParams} from '@remix-run/react';
import { TypingSearchBar } from '~/components/TypingSearchbar';


export default function Search() {
    const params = useParams();

    return(
        <TypingSearchBar isHome={false} params={ params } variant="search"/>
    );

}
Enter fullscreen mode Exit fullscreen mode

Wrapping up

In this article, we've taken a deep dive into creating an animated typing search bar using React and Remix. This interactive element not only serves an aesthetic purpose but also encourages user engagement on your e-commerce site. We’ve walked through the code step by step, understanding the key principles behind the implementation.
If you're curious to know how to implement this in vanilla JS and HTML, just let us know in the comments and we'll be sure to create a guide for that too!
We hope this tutorial sparks your interest and paves the way for further exploration.
Happy coding!

Top comments (0)