npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

hepa

v0.0.5

Published

Because it filters, get it? This is a set of [compound components](https://www.youtube.com/watch?v=hEGg-3pIHlE) to make filtering data easier.

Downloads

18

Readme

Hepa

Because it filters, get it? This is a set of compound components to make filtering data easier.

NPM badge Discord badge CircleCI build

Problem:

When iterating on different filter controls for data, a big impediment is wiring up the controls to where the actual data is available. Maybe the filter controls are in a sidebar, and the data is being used in the main page, and there are several components in between them in the tree.

Solution:

Because these components use context to communicate, they can be used in any combination without any manual wiring. Put a FilterProvider at the top, add some custom FilterControls (or one of the built in controls), and pass data into a Filter component.

Example usage

<FilterProvider>
  <div>
    <Exact name="name" />
    <Search keys={["name", "email", "id"]}>
    <Fuzzy name="email" />
  </div>
  <ul>
    <Filter
      data={rawData}
      render={filteredData =>
        filteredData.map(datum => (
          <li>
            {datum.name}, {datum.email}
          </li>
        ))
      }
    />
  </ul>
</FilterProvider>

The top level FilterProvider wraps a set of controls. Think of it how you would a form, rather than how Redux or react-router's top level providers work. Instead of a single provider for your whole app, it should only wrap the controls you want to apply to a given Filter.

Custom filter controls

For instance, we could reuse the Exact control to create a select of possible options.

import { Exact } from "hepa";

export default const SelectFilter = ({ children }) =>
  <Exact
    name={this.props.name}
    render={(innerProps) => (
      <select {...innerProps}>
        {this.props.children}
      </select>
    )}
  />

/**
 * Used like:
 * <SelectFilter name="customerId">
 *  <option value="1">Google</option>
 *  <option value="2">Facebook</option>
 *  <option value="3">Apple</option>
 *  <option value="4">Microsoft</option>
 * </SelectFilter>
 */

API Reference

FilterProvider

The top level component that owns filter state from controls below it in the hierarchy. Accepts no props, silently communicates with child controls via context.

Filter

data: An array of items to filter.

render: A function with the signature data => ReactElement.

WithFilteredData

data: An array of items to filter.

Same as the Filter component, but a higher order component. It passes the data prop onto the wrapped component after filtering.

FilterControl

mapValuesToComparison: This method gets passed each item in the array to be filtered, and maps it to an intermediate value that gets used in compare. The signature is dataItem => valueToCompare

compare: This is a higher order function that gets passed the output from the other prop method, then the current value of the filter control—what the user typed, by default. The signature is filterValue => valueToCompare => boolean

value (optional): By default, this component will handle its own state. It allows you to control it, however, which enables more complex use cases. This value is what is passed into compare as the first argument.

onChange (optional): Along with value, allows for this to be treated as a controlled component, which enables more complex use cases.

render (optional): By default, it renders a single input element. More complex filter controls, like select boxes or ranges, might need different rendered output. This gets passed all props from FilterControl except mapValuesToComparison and compare.

In the included Exact component, the two required props are as below.

mapValuesToComparison = 
  dataValue => dataValue[this.props.name]

compare = (filterValue /* a string value typed by the user */) => 
  (intermediateValue /* the string returned from `mapValuesToComparison` */) =>
    dataValue === filterValue;

Search provides an example of why this combination can be very powerful. It looks at multiple keys to see if any contain the filter value as a substring.

mapValuesToComparison = (datum) => {
  const { keys } = this.props;

  const values = keys.reduce((out, key) => {
    out.push(datum[key].toString().toLowerCase());
    return out;
  }, []);

  return values;
};
compare = (filterValue /* a string value typed by the user */) => 
  (dataValues /* the array returned from `mapValuesToComparison` */) =>
    dataValues.some(value => value.includes(filterValue));

Exact

This performs an exact string comparison (===) on what is entered into the filter with the value on the data keyed of what's passed in as the name prop. For instance, a data value with the shape of { id: 1, username: 'vcarl' } would match <Exact name="username"> with "vcarl" entered.

Fuzzy

This performs a "fuzzy" match akin to Sublime Text's cmd-p menu on what is entered into the filter with the value on the data key passed in as the name prop. For instance, a data value with the shape of { id: 1, username: 'vcarl' } would match <Fuzzy name="username"> with "val" entered, because vcarl

Search

This checks if multiple keys contain what is entered into the filter as a substring, with the value on the data keys passed in as the name prop. For instance, a data value with the shape of { id: 1, username: 'vcarl', email: '[email protected]' } would match <Search keys={["username", "email"]}> with "@me.com" entered.