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.
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';
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
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
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 theisVisible
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 theisVisible
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 andword
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 fromword
. 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 theword
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 resettingword
,isVisible
, andsubIndex
to their initial states, and incrementing index by 1 (or resetting it to 0 if it's at the end of thewords
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]);
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>
);
}
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>
);
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"/>
);
}
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)