Typeahead with React and ActiveJS
Tags: react, tutorial, javascript, webdev
Published: 2020-11-30 20:59:11

We're going to build a Typeahead component using React, ActiveJS, and RxJS. We'll search for Wikipedia articles in the example but we can point it to any REST API once we have the setup in place.

## Table of contents
| S.No. | Content |
| -- | -- |
| 1. | [HTTP Request logic using Observable HTTP API - Provided by RxJS](#http-service) |
| 2. | [Asynchronous State Management - Provided by ActiveJS](#state-management)|
| 3. | [Query and response handling](#query-and-response) |
| 4. | [React Component](#react-component) |

### 1. HTTP Request logic <a name="http-service"></a>

First, we create a function that takes the search string and fetches results from the Wikipedia API using the `ajax` HTTP utility provided by `RxJS`.

```ts
function getWikipediaArticles(title: string): Observable<any> {
  const url = "https://en.wikipedia.org/w/api.php";
  const params = new URLSearchParams({
    search: title, // the articles to search for
    action: "opensearch",
    format: "json", // API response type
    origin: "*", // to allow CORS requests
    limit: 3 // maximum number of matched results
  });
  return ajax.getJSON(url + "?" + params);
}
```

`ajax.getJSON` returns a cold Observable, i.e. It will only make the HTTP request when we subscribe to it. And canceling a pending request is as easy as unsubscribing from this Observable, which is a necessity for the Typeahead because we want to keep only one request active at a time to prevent race conditions and save some resources.

`URLSearchParams` is a native API that, among other things, can easily convert and encode an `object` into query-parameters. e.g.: `{a: 'b', c: 'd&d'}` becomes `a=b&c=d%26d`.

### 2. State Management using ActiveJS AsyncSystem <a name="state-management"></a>
To handle all the nitty-gritty aspects of an asynchronous task we'll use an AsyncSystem, it takes care of all the state-management we're going to need for the Typeahead. We'll pass our search-query, response-data, and response-error through it, and access the same from it whenever/wherever we need them.
```ts
export const searchSystem = new AsyncSystem<string, any, any>({
  QUERY_UNIT: { dispatchDebounce: true }
});
```

`dispatchDebounce` exactly does what it implies, it debounces the queries for `200ms` by default, and we can also pass a custom number if we want.

The AsyncSystem gives us four **Observable data Units** pertaining to every aspect of an asynchronous API request. We'll extract these data Units for ease of use.

```ts
// extract the Observable data Units for easier access
const {queryUnit, dataUnit, errorUnit, pendingUnit} = searchSystem;
```
`queryUnit` to store, and share the queries, and to trigger the API call
`dataUnit` to store, and share the response-data
`errorUnit` to store, and share the response-error
`pendingUnit` to store, and share the pending-status (This happens automatically. When we dispatch to `queryUnit` it becomes `true`, and when we dispatch to `dataUnit` or `errorUnit` it becomes `false`)

### 3. Query and Response handling <a name="query-and-response"></a>
We already have HTTP Service and State Management in place, now we just need to connect them together and set up the mechanism for Typeahead, such that whenever the `queryUnit` emits a value we trigger a search request, and also cancel any pending request at the same time.

```ts
// setup a stream using RxJS operators,
// such that at a time only one request is active
const searchStream = queryUnit.future$ // listen for future values emitted by queryUnit, so that it doesn't start making requests immediately
  .pipe(
    filter(query => { // process the typed query
      if (query.trim()) {
        return true; // only proceed if non-empty string
      }
      dataUnit.clearValue(); // if query is empty, clear the data
      return false; // don't go any further
    }),

    // switchMap to ensure only one request at a time
    switchMap(query =>
      // create a new HTTP request Observable
      getWikipediaArticles(query).pipe(
        // format the data, to make it easy to consume
        map(formatSearchResults),
        // dispatch the formatted data to dataUnit
        tap(data => dataUnit.dispatch(data)),

        catchError(err => {
          errorUnit.dispatch(err); // disptach the error
          return EMPTY; // don't let the stream die
        })
      )
    )
  )
  .subscribe(); // activate the stream

// parse and format the data recieved from the Wikipedia REST API
// just trust me on this one ;) it takes the response from the Wikipedia API
// and turns it into an Array of {title: string, url: string} objects
function formatSearchResults([query, titles, noop, urls]) {
  return titles.map((title: string, i: number) => ({
    title,
    url: urls[i]
  }));
}
```

### 4. React Component <a name="http-service"></a>
We're in the endgame now, we just need a simple React component and Hooks to finalize our Typeahead.

```tsx
function App() {
  // create local state to hold the Typeahed data
  const [systemValue, setValue] = useState();
  // extract the data for easier access
  const {query, data, error, pending} = systemValue || {};

  // subscribe to the value changes in the searchSystem the 
  // it will update our local state and trigger re-rendering
  useEffect(() => {
    const subscription = searchSystem.subscribe(setValue);
    return () => subscription.unsubscribe(); // prevent memory leak
  }, []);

  // dispatch the input value to queryUnit
  // to trigger new requests and start the whole process
  const handleInput = e => queryUnit.dispatch(e.target.value)

  // a rudimentary UI with essential components
  return (
    <React.Fragment>
      <input
        onChange={handleInput}
        placeholder="Search Wikipedia, eg: Big Bang"
      />

      {query && 
      <p>
        IsPending: <b>{pending ? 'Yes' : 'No'} </b> |
        Error: <b>{error || 'NA'}</b>
      </p>
      }

      <ul>
        {data?.map(item => 
          <li>
            <a href="{item.url}" target="_blank" rel="noopener">
              {item.title}
            </a>
          </li>
        )}
      </ul>
    </React.Fragment>
  );
}

// render the component
render(<App />, document.getElementById("root"));
```

That's it, folks, we're done!

Here's the result of our labor.

{% stackblitz react-ts-spn9ze view=preview %}

Let me know if it was helpful, or if it's too much too fast.

> If you liked the solution, then you'd like what ActiveJS has to offer, it's a one-stop solution for all your State Management needs, be it sync or async, persistence or immutability, time-travel or less-code, it's all there. (no reducers though)

Cheers

[🌏 ActiveJS Website](https://activejs.dev)
[📖 ActiveJS Documentation](https://docs.activejs.dev)
[🤾‍♂️ ActiveJS Playground](https://activejs.dev/#/playground/asyncsytem)
[💻 ActiveJS GitHub Repo](https://github.com/activejs/activejs) (drop a ⭐ maybe :)