cuando

Easy fluent interface for creating conditional expressions


Keywords
conditional, conditional-expressions, javascript, typescript, typescript-library, zero-dependencies
License
MIT
Install
npm install cuando@0.0.1

Documentation

Quando

npm bundle size GitHub Language grade: JavaScript

🎵 Tell me when will you be mine? / Tell me quando quando quando 🎵

Install

npm i quando
yarn add quando

Motivation

JavaScript has poor support for conditional expressions, and handling scenarios with more than three clauses is impossible. It's awkward when handling conditions in JSX which encourages an expression oriented approach and doesn't provide conditional elements found in a templating language like Handlebars.

Some proposals would bring more powerful conditional expressions to JavaScript like do and pattern matching, but they are a long way off and may never happen. In an ideal world, this library would be made redundant by these proposals making it into the language. However, Quando is for the time we spend waiting.

Here's an example that handles the status of an API call using the Match function:

function Example() {
  const { status, error, data } = useQuery("repoData", () =>
    fetch("https://api.github.com/repos/richeyryan/quando").then(res =>
      res.json(),
    ),
  );

  return (
    <div>
      {Match(status)
        .where("error", () => <>"An error has occured: " {error.message}</>)
        .where("loading", "Loading...")
        .end(() => (
          <>
            <h1>{data.name}</h1>
            <p>{data.description}</p>
            <strong>👀 {data.subscribers_count}</strong>{" "}
            <strong> {data.stargazers_count}</strong>{" "}
            <strong>🍴 {data.forks_count}</strong>
          </>
        ))}
    </div>
  );
}

Functions

Compatibility

The API aims to avoid clashing with current or potential future JavaScript features. We avoid reserved words where possible, and When is capitalized to avoid conflicts with the pattern matching proposal.

Fluent Interface

Each function has a chainable API or fluent interface. You must complete the chain with the end method, which will return the result of your expression. You can also provide the default value with the end method if there are no matches in your condition.

Lazy Evaluation

The result parameter of any condition can be a function. If the condition is truthy, the function gets called, which prevents issues like in the example above where we get an error if we try to read data.name before it's defined.

When

The equivalent of an if statement, returns the first clause where the condition is truthy. Returns the default if there is no match.

import { When } from "quando";

When(status === "loading", <Loading />)
  .elseWhen(status === "error", <Error message="Failed to load user" />)
  .elseWhen(status === "success", <User user={user} />)
  .end();

Unless

Returns the clause if the condition is falsy. Returns the default if the condition is truthy.

import { Unless } from "quando";

Unless(user, "Select a user to continue");

Match

A very rough approximation of pattern matching. Basically an expression version of a switch statement.

The first parameter of where can be a value that strictly matches (===) the comparable passed to Match. Otherwise, it can be a function which takes the comparable as a parameter and returns true if it matches or false if it doesn't.

The second parameter can be any value or a function that is called only when the comparable is matched.

Match("John")
  .where("John", "guitar")
  .where("George", "guitar")
  .where("Paul", "bass")
  .where("Ringo", "drums")
  .end();

Examples

Examples of using each function can be found in the examples folder. You can run them like this:

yarn build
node examples/examples.js

Any contributions of interesting examples would be much appreciated.

Extending the library

Each of the functions above are factories that create an instance of a class. There is a class for each of the functions. Each class is a facade over the Matcher class, they make the public fluent interface and each call passes through to the Matcher which handles the actual matching logic.

You can import the Matcher class and the extractResult function which helps with results that are passed as functions. Using them both you can create your own factory function and inject your own matching logic into the Matcher class.