Thursday, June 16, 2022
HomeWeb DevelopmentCreate a customizable React search element with autocomplete

Create a customizable React search element with autocomplete


Most frontend purposes at present require a search field of some type, which typically is the primary element a person interacts with in your web pageā€”take Airbnb, Uber, or Google Maps as an illustration. Making a search element that not solely works however is purposeful sufficient to information your person in finishing their desired process is significant in your utilityā€™s person expertise.

Turnstone is a brand new library that allows React builders to do exactly that. This light-weight library (12.2kB Gzip) ships with autocomplete, automated caching, WAI-ARIA accessibility, and different options that allow you to construct a purposeful and accessible search element.

Turnstone parts are simply customizable utilizing varied CSS strategies like CSS modules or Tailwind CSS. On this article, weā€™ll use Turnstone and Tailwind to create an utility that queries the Marvel Comics API to seek for characters belonging to the Marvel Cinematic Universe.

Working Search Bar

View the stay venture and the supply code on GitHub.

Stipulations

To observe together with this tutorial, youā€™ll want:

  • Fundamental understanding of the React framework
  • An API key from the Marvel Comics API

Tailwind CSS information is appreciated, however not required.

Getting your API key and including referrer websites

Head to the Marvel Builders website and register a brand new account. Then navigate to My Developer Account the place youā€™ll discover your private and non-private API keys. Copy the general public key for later.

Earlier than your app could make a request to this API, its area identify needs to be included in your listing of referrer websites. On the identical web page, scroll all the way down to the referrer websites part, and add the area identify of your app, i.e., localhost. You can too use an asterisk * to just accept requests from all domains, though this isn’t totally protected.

Now youā€™re all set to start out growing!

Turnstone options

The Turnstone element accepts a wide range of properties used to manage totally different components of the search field. All the pieces from the styling of the element, what information supply the search field queries from, error messages, and extra could be configured with the suitable prop.

Letā€™s go over a number of the necessary ones we are going to use in constructing this utility.

typeahead
Sort: boolean
typeahead ā€” aka autocomplete ā€” is a function wherein an utility predicts the remainder of a phrase a person is typing. That is set to true by default.

maxItems
Sort: quantity
This property controls the utmost variety of search outcomes displayed within the listbox.

listbox
Sort: object, array, or operate
listbox specifies how outcomes are rendered in response to a personā€™s question. This property controls the supply of information in addition to the search kind ā€” which may very well be both startsWith or comprises.

As an object, listbox queries a single information supply as such:

const listbox = {
    displayField: 'characters',
    information: (question) =>
      fetch(`/api/characters?q=${question}`)
        .then(response => response.json()),
    searchType: 'startsWith',
}

return (
  <Turnstone listbox={listbox} />
)

information above is a operate whose return worth ought to be a Promise that resolves to an array of things. This operate takes the present question string as an argument, and reruns every time the string adjustments ā€” which is why we’d like one other prop known as debounceWait, extra on that momentarily.

If used as an array, listbox can accumulate information from a number of sources:

const listbox = [
  {
    id: 'cities',
    name: 'Cities',
    ratio: 8,
    displayField: 'name',
    data: (query) =>
      fetch(`/api/cities?q=${encodeURIComponent(query)}`)
        .then(response => response.json()),
    searchType: 'startswith'
  },
  {
    id: 'airports',
    name: 'Airports',
    ratio: 2,
    displayField: 'name',
    data: (query) =>
      fetch(`/api/airports?q=${encodeURIComponent(query)}`)
        .then(response => response.json()),
    searchType: 'contains'
  }
]

return (
  <Turnstone listbox={listbox} />
)

On this state of affairs, a ratio property can be utilized to specify the variety of outcomes that occupies listbox in relation to maxItems. This implies, if maxItems is about to 10 for instance, the ratio quantity from every information supply ought to add as much as 10.

types
Sort: object
An object whose keys symbolize parts rendered by Turnstone. Every corresponding worth is a string representing the class attribute for the factor

const types = {
enter: 'w-full h-12 border border-slate-300 py-2 pl-10',
  listbox: 'w-full bg-white sm:border sm:border-blue-300 sm:rounded text-left sm:mt-2 p-2 sm:drop-shadow-xl',
  groupHeading: 'cursor-default mt-2 mb-0.5 px-1.5 uppercase text-sm text-rose-300',
} 

return (
  <Turnstone types={types} />
)

We are able to see how simply Tailwind suits in to make the styling course of simpler. View the listing of obtainable Turnstone parts within the docs.

debounceWait
Sort: quantity
This property specifies the wait time ā€” in milliseconds ā€” after the person finishes typing earlier than their question is shipped to the fetch operate.

defaultListbox
This property is equivalent to listbox however is displayed when the search field is in focus, and not using a question string. It’s often used to create a listbox for current searches:

const defaultListBox =  [])


return (
  <Turnstone defaultListBox={defaultListBox} />
)

Creating the Software

Open up your terminal and create a brand new React utility with the next command:

npx create-react-app turnstone-demo

After the set up is full, navigate into the ventureā€™s listing:

cd turnstone-demo

And set up Turnstone and Tailwind CSS ā€” alongside its peer dependencies, PostCSS and Autoprefixer:

npm set up -D turnstone tailwindcss postcss autoprefixer

Letā€™s begin out by creating an setting variable for the API key. In your ventureā€™s root, create a .env file and retailer the API key

// .env
REACT_APP_MARVEL_APIKEY = 'your_apikey_here'

Create React App gives help for environmental variables, that are created with the required REACT_APP_ prefix. This variable can then be accessed throughout the app as course of.env.REACT_APP_MARVEL_APIKEY.

N.B., bear in mind so as to add .env to your .gitignore file so that you donā€™t expose your key in a public repository.

Picture Backdrop

The underlying picture backdrop as seen within the venture demo is created with the next CSS class:

// App.css
.image-backdrop {
  background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('../public/comic-backdrop.jpg');
  peak: 100vh;
  width: 100%;
  background-size: cowl;
  background-position: middle;
  background-repeat: no-repeat;
}

Connect this class to the physique tag in public/index.html and you must have a picture backdrop for the search field to be positioned over:

// public/index.html
<!DOCTYPE html>
<html lang="en">
  <head> 
    <!-- markup -->
  </head>
  <physique class="image-backdrop"> 
    <!-- extra markup -->
    <div id="root"></div>
  </physique>
</html>

Initializing Tailwind

To initialize Tailwind CSS, run the next command:

npx tailwindcss init -p

This generates the tailwind.config.js and postcss.config.js recordsdata, that are used to customise and prolong Tailwindā€™s options.

We solely must configure the template paths for now. Replace tailwind.config.js with the code beneath:

// tailwind.config.js
module.exports = {
  content material: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    prolong: {},
  },
  plugins: [],
}

Subsequent, add the Tailwind layers to index.css utilizing the @tailwind directive:

// index.css
@tailwind base;
@tailwind parts;
@tailwind utilities;

And now you can begin styling your React app with Tailwindā€™s utility courses. Letā€™s start by positioning the SearchBox element on the prime middle of the display screen.

In src, create a parts folder and retailer the SearchBox.js file. Subsequent, import this element into App.js and apply the Tailwind courses beneath to the mother or father container:

// App.js
import SearchBox from './parts/SearchBox'
import './App.css'

operate App() {
  return (
    <div className="m-auto relative top-28 w-11/12 sm:w-6/12">
      <SearchBox />
    </div>
  )
}
export default App

This positions the search field on the prime middle of the web page.

Utilizing the Turnstone element

Earlier than we begin to configure the dynamic components of the search field, add the next properties to the Turnstone element:

// SearchBox.js
import Turnstone from 'turnstone'

const SearchBox = () => {
  return (
    <Turnstone
      id='search'
      identify="search"
      autoFocus={true}
      typeahead={true}
      clearButton={true}
      debounceWait={250}
      listboxIsImmutable={true}
      maxItems={6}
      noItemsMessage="We could not discover any character that matches your search"
      placeholder="Seek for any character within the MCU"
    />
  )
}
export default SearchBox

clearButton renders a transparent button at any time when the person enters a personality into the search field.

autoFocus set to true makes the search field routinely obtain focus when the web page hundreds.

maxItems units the utmost variety of search outcomes to be displayed within the listbox to six.

listboxIsImmutable set to true ensures that the contents of listbox doesnā€™t change between queries; i.e., the identical question canā€™t return totally different outcomes.

Now letā€™s transfer on to the listbox property.

listbox

In listboxā€™s information property, we make a request to the Comics API, attaching the present question string and your API key within the course of:

// SearchBox.js
import Turnstone from 'turnstone'

const SearchBox = () => {
  const listbox = {
    displayField: 'characters',
    information: async (question) => {
      const res = await fetch(
        `https://gateway.marvel.com:443/v1/public/characters?nameStartsWith=${question}&apikey=${course of.env.REACT_APP_MARVEL_APIKEY}`
      )
      const information = await res.json()
      return information.information.outcomes
    },
    searchType: 'startsWith',
  }

  return (
    <Turnstone
      id='search'
      identify="search"
      autoFocus={true}
      typeahead={true}
      clearButton={true}
      debounceWait={250}
      listboxIsImmutable={true}
      maxItems={6}
      noItemsMessage="We could not discover any character that matches your search"
      placeholder="Seek for any character within the MCU"
      listbox={listbox}
    />
  )
}
export default SearchBox

The Marvel API has an Interactive Documentation web page the place all obtainable endpoints are listed. In our case, weā€™ve made a request to the characters endpoint: /v1/public/characters.

Further parameters like tales, occasions, or nameStartsWith could be added to get totally different outcomes. We additionally use the nameStartsWith parameter, setting its worth to the question string.

The results of this operate ought to be an object containing a outcomes array of all Marvel characters whose identify begins with the question string:

// JSON end result from API name. question="Physician Unusual"
{
  "code": 200,
  "standing": "Okay",
  "copyright": "Ā© 2022 MARVEL",
  "attributionText": "Knowledge supplied by Marvel. Ā© 2022 MARVEL",
  "attributionHTML": "<a href="http://marvel.com">Knowledge supplied by Marvel. Ā© 2022 MARVEL</a>",
  "etag": "07a3a76164eec745484f34562db7ca7166c196cc",
  "information": {
    "offset": 0,
    "restrict": 20,
    "whole": 2,
    "rely": 2,
    "outcomes": [
      {
        "id": 1009282,
        "name": "Doctor Strange",
        "description": "",
        // ...

The relevant data is located in data.results, which is the functionā€™s return value.

At this point, the application functions properly. Now we can proceed to style Turnstoneā€™s elements with Tailwind and the styles property.

Styling Turnstone elements with Tailwind

As explained earlier, keys in the styles object represent a certain element of the search component. We can style elements like the listbox, highlighted items in the listbox, and even the color of the autocomplete text to create a better looking search box:

// SearchBox.js
import Turnstone from 'turnstone'
import recentSearchesPlugin from 'turnstone-recent-searches'

const listbox = {
  // ...
}

const styles = {
  input: 'w-full border py-2 px-4 text-lg outline-none rounded-md',
  listbox: 'bg-neutral-900 w-full text-slate-50 rounded-md',
  highlightedItem: 'bg-neutral-800',
  query: 'text-oldsilver-800 placeholder:text-slate-600',
  typeahead: 'text-slate-500',
  clearButton:
    'absolute inset-y-0 text-lg right-0 w-10 inline-flex items-center justify-center bg-netural-700 hover:text-red-500',
  noItems: 'cursor-default text-center my-20',
  match: 'font-semibold',
  groupHeading: 'px-5 py-3 text-pink-500',
}

const SearchBox = () => {
  return (
    <Turnstone
      id='search'
      name="search"
      autoFocus={true}
      typeahead={true}
      clearButton={true}
      debounceWait={250}
      listboxIsImmutable={true}
      maxItems={6}
      noItemsMessage="We couldn't find any character that matches your search"
      placeholder="Search for any character in the MCU"
      listbox={listbox}
      styles={styles}
    />
  )
}

export default SearchBox

Black Panther in Search

Item component prop

Although we can style items in listbox by referencing the item property in styles, Turnstone provides component properties that essentially allow extra customization and formatting of Turnstone elements.

Hereā€™s how we can use this to include an avatar beside the characterā€™s name in a search result:

// SearchBox.js
import Turnstone from 'turnstone'

const listbox = {
    // ...
}

const styles = {
    // ...
}

const Item = ({ item }) => {
  /* thubmnails from the API are stored as partials therefore 
    we have to concatenate the image path with its extension
  */
  const avatar = `${item.thumbnail.path}.${item.thumbnail.extension}`
  return (
    <div className="flex items-center cursor-pointer px-5 py-4">
      <img
        width={35}
        height={35}
        src={avatar}
        alt={item.name}
        className="rounded-full object-cover mr-3"
      />
      <p>{item.name}</p>
    </div>
  )
}

const SearchBox = () => {
  return (
    <Turnstone
      id='search'
      name="search"
      autoFocus={true}
      typeahead={true}
      clearButton={true}
      debounceWait={250}
      listboxIsImmutable={true}
      maxItems={6}
      noItemsMessage="We couldn't find any character that matches your search"
      placeholder="Search for any character in the MCU"
      listbox={listbox}
      styles={styles}
      Item={Item}
    />
  )
}

export default SearchBox

Thanos in Search

Recent searches plugin

For an extra (1.7kB gzip), another package called turnstone-recent-searches can be added to Turnstoneā€™s plugin prop to automatically record the userā€™s recent searches.

Install this package with the following command:

npm install turnstone-recent-searches

And include it in the plugins prop as such:

// SearchBox.js
import Turnstone from 'turnstone'
import recentSearchesPlugin from 'turnstone-recent-searches'

const styles = {
  input: 'w-full border py-2 px-4 text-lg outline-none rounded-md',
  listbox: 'bg-neutral-900 w-full text-slate-50 rounded-md',
  highlightedItem: 'bg-neutral-800',
  query: 'text-oldsilver-800 placeholder:text-slate-600',
  typeahead: 'text-slate-500',
  clearButton:
    'absolute inset-y-0 text-lg right-0 w-10 inline-flex items-center justify-center bg-netural-700 hover:text-red-500',
  noItems: 'cursor-default text-center my-20',
  match: 'font-semibold',
  groupHeading: 'px-5 py-3 text-pink-500',
}

const listbox = {
  displayField: 'characters',
  data: async (query) => {
    const res = await fetch(
      `https://gateway.marvel.com:443/v1/public/characters?nameStartsWith=${query}&apikey=${process.env.REACT_APP_MARVEL_APIKEY}`
    )
    const data = await res.json()
    return data.data.results
  },
  searchType: 'startsWith',
}

const Item = ({ item }) => {
  const avatar = `${item.thumbnail.path}.${item.thumbnail.extension}`
  return (
    <div className="flex items-center cursor-pointer px-5 py-4">
      <img
        width={35}
        height={35}
        src={avatar}
        alt={item.name}
        className="rounded-full object-cover mr-3"
      />
      <p>{item.name}</p>
    </div>
  )
}

const SearchBox = () => {
  return (
    <Turnstone
      id='search'
      name="search"
      autoFocus={true}
      typeahead={true}
      clearButton={true}
      debounceWait={250}
      listboxIsImmutable={true}
      maxItems={6}
      noItemsMessage="We couldn't find any character that matches your search"
      placeholder="Search for any character in the MCU"
      listbox={listbox}
      styles={styles}
      Item={Item}
      plugins={[recentSearchesPlugin]}
    />
  )
}

export default SearchBox

This function is equally necessary because it creates a greater expertise in your person.

Recent Searches

Conclusion

Autocomplete search bins are prevalent in trendy UI design, and having a React library that helps us simply implement them is nice.

Turnstoneā€™s documentation does job at explaining its API design, giving it a gradual studying curve ā€” which wasnā€™t the case after I tried different React autocomplete libraries. To see extra examples of Turnstone in motion, take a look at the examples on Turnstoneā€™s web site.

Full visibility into manufacturing React apps

Debugging React purposes could be troublesome, particularly when customers expertise points which can be arduous to breed. If you happen toā€™re concerned with monitoring and monitoring Redux state, routinely surfacing JavaScript errors, and monitoring sluggish community requests and element load time, strive LogRocket.

LogRocket is sort of a DVR for internet and cellular apps, recording actually every little thing that occurs in your React app. As a substitute of guessing why issues occur, you may mixture and report on what state your utility was in when a problem occurred. LogRocket additionally screens your app’s efficiency, reporting with metrics like consumer CPU load, consumer reminiscence utilization, and extra.

The LogRocket Redux middleware package deal provides an additional layer of visibility into your person periods. LogRocket logs all actions and state out of your Redux shops.

Modernize the way you debug your React apps ā€” .

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments