Declaring your business 👔 logic like React ⚛️
Tags: javascript, react, npm, opensource
Published: 2020-11-10 22:07:24

I published a npm library that allows you to write your conditional business logic in a declarative way like React does. It is currently being used at [HackerRank](https://www.hackerrank.com/) in production and has made our code more __maintainable and readable__ especially when it comes to complex rendering logic.

Before I dive into what this library does, let's understand the difference between the two approaches.  

##What is the difference between declarative and imperative programming?

According to [wiki](https://en.wikipedia.org/wiki/Imperative_programming):

In *__Imperative Programming__* paradigm you manipulate the state of the program directly using statements to achieve the desired behavior.

In *__Declaraive Programming__* paradigm you focus on what the program should achieve rather than how to achieve it.

I don't get your gibber jabber, tell me in code.

Imagine a simple UI component, such as a "Submit" button which submits form data. While we wait for the request to return from the server we would want to disable the button.

```js
if(isLoading) {
  button.disabled = true;
}
```
To achieve the disabled state, manipulate the UI like this ^.

In contrast, the declarative approach would be:
```js
return <Button disabled={isLoading} />;
```
Because the declarative approach separates concerns, this part of it only needs to handle how the UI should look in a specific state, and is therefore much simpler to understand.

so coming back to the point:

##So, how can you declare your conditional business logic like React?
A couple of months ago, I published a [__library__](https://github.com/naman03malhotra/match-rules) on [__npm__](https://www.npmjs.com/package/match-rules) called `match-rules` that can turn your code from:

```js
function isUserLocked(user: User) {
  // some messed up legacy locking logic data from backend
  if (
    user?.flagged === true &&
    user?.blocked === true &&
    (user?.is_locked === 0 || user?.is_locked === "LOCKED") && 
    user?.profile?.account_deleted === true
  ) {
    return true;
  }

  return false;
}

function showWarning(user: User) {
  return isUserLocked(user) && user?.show_warning;
}

function showAccountDisabled(user: User) {
  return isUserLocked(user) && user?.profile?.show_account_disabled;
}

if (isUserLocked(user)) {
  // render account locked UI
}

if (showWarning(user)) {
  // render warning UI or something else
}

if (showAccountDisabled(user)) {
  // render account disabled UI
}
```
to

```js
import matchRules from 'match-rules';

import {
  IS_USER_LOCKED_RULE,
  SHOW_WARNING_RULE,
  SHOW_ACCOUNT_DISABLED_RULE
} from './rules';

// user object can be served from the app state 
if (matchRules(user, IS_USER_LOCKED_RULE)) {
  // render user locked UI
}

if (matchRules(user, SHOW_WARNING)) {
  // show warning UI
}

if (matchRules(user, [IS_USER_LOCKED_RULE, SHOW_ACCOUNT_DISABLED_RULE])) {
  // render account disabled UI
}
```
where your rules can reside in `rules.js` with an object like structure:

```js
export const IS_USER_LOCKED_RULE = {
  flagged: true,
  blocked: true,
  is_locked: (value, sourceObject) => value === 0 || value === "LOCKED",
  profile: {
   account_deleted: true,
  },
};

export const SHOW_WARNING_RULE = {
  ...IS_USER_LOCKED_RULE,
  show_warning: true,
};

export const SHOW_ACCOUNT_DISABLED_RULE = {
  profile: {
    show_account_disabled: true,
  },
};
```
Let's have a look at a couple of advantages of declaring the conditional logic in a declarative way:

* It __reduces cognitive complexity considerably__: if you observe `IS_USER_LOCKED_RULE` it vividly describes what all conditions need to be met as compared to `isUserLocked` function. The object structure is more readable.

* You can __compose and pass multiple__ rules: compose/extend multiple rules to form new rules, which avoids repetition. Also, you can pass multiple rules object as an `Array` of rules. 
Multiple rules are by default are compared with `and` operator, you can also compare them using `or` operator by passing `{ operator: 'or' }` prop in options. You can read more about this on [docs](https://github.com/naman03malhotra/match-rules). 
We composed a new rule by extending `IS_USER_LOCKED_RULE`
```js
export const SHOW_WARNING_RULE = {
  ...IS_USER_LOCKED_RULE,
  show_warning: true,
};
```
In an Object-based structure, you can easily extend rules without introducing complexity.

* __Save time with unit tests__: you don't have to write specific unit tests for RULES object, at max you can do snapshot testing if you wish to. `match-rules` handle rule matching logic for you, so you don't have to write specs.

* __Write your logic in its true form__: since the data structure in a JavaScript of the source is mostly an Object. It makes sense to define your conditions in an Object as well, this way __you don't have to destructure the Object__. It especially helps if your object is deeply nested.
In our example, the `status` key was nested inside the profile object. The RULE we wrote had the same structure and with the expected value.
```js
profile: {
  account_deleted: true,
},
```

* Handle __complex conditions using functions__: so far it is capable to handle any condition since you can write your own functions in the rule. 
when it encounters a function it passes the value (as the first parameter) and original source object (as the second parameter) from the source to that function matching the corresponding key of that level. The same happened in the above example when it encountered the `is_locked` key.
```js
is_locked: (value, sourceObject) => value === 0 || value === "LOCKED"
```
Using a combination of key's value and original source object you can handle complex conditions. You have to write spec just for this function.


####So, I consolidated my thoughts into a library and called it `match-rules`

![Add match-rules](https://dev-to-uploads.s3.amazonaws.com/i/0dxpzmw11b68vq6jnodw.jpg)

Think of it more of a practice as it works on the principles we just discussed.

If I have to give a precise definition, it would be:


__*`match-rules` is a tiny (1kB GZipped) zero dependency JavaScript utility that lets you write your conditional business logic in a declarative way.*__

It can be used with feature flags, complex conditions, conditional rendering, and the rest is your imagination.

## How it works?
The way `match-rules` work is, it checks for each key in the `RULES` object for the corresponding key in the source object. It does so by treating the RULES object like a tree and recursively going through each key until there are no nodes left. Rules generally contain a small subset of keys from the source object, it can be an exact replica of the complete object as well, with expected values.

####How to use it and detailed documentation:

`yarn add match-rules` or `npm install --save match-rules`

__API__ of matchRules looks like this:
```js
import matchRules from 'match-rules';

// returns a boolean value.
matchRules(
  sourceObject, // can be any object with data.
  RULES_OBJECT, // you can also pass multiple rules in an array [RULE_ONE, RULE_TWO],
  options, // (optional)
);

const options = {
  operator: 'and', // (optional, default: 'and') in case of multiple rules you can pass 'and' or 'or'. In the case of 'or,' your rules will be compared with 'or' operator. Default is 'and'
  debug: true, // (optional, default: false) when debug is true, it logs a trace object which will tell you which rule failed and with what values of source and rules object.
};
 
// NOTE: all the rules inside a single rule are concatenated by 'and' operator by default.
```

For examples and detailed documentation please visit the __[Github repo](https://github.com/naman03malhotra/match-rules).__


##People __Involved__
#### A big thanks to
[Sudhanshu Yadav](https://twitter.com/_syadav) for code review, design discussion, feedback, and coming up with the name `match-rules` :p
[Aditya](https://twitter.com/dev__adi) for reviewing this article thoroughly, constructive feedback, and recommending this blog site.
[Vikas](https://twitter.com/vicode_in) for reporting a critical bug and feedback for this article.

##Current __Status__

It is stable with 100% code coverage and is currently being used at [HackerRank](https://www.hackerrank.com/) in Production.

`match-rules` does not have any dependency and it is only 1kB (GZipped) in size.

Feel free to send across a Pull Request if it doesn't fit your use-case.

So next time when you are about to write conditional rendering logic. Try this library. You'll thank me later xD.

Show some support, leave a star if you find it useful.
__GitHub:__ https://github.com/naman03malhotra/match-rules
__npm:__ https://www.npmjs.com/package/match-rules
__Live Example:__ https://stackblitz.com/edit/match-rules

Also checkout my other [open-source project](https://github.com/naman03malhotra/auto-skip-for-netflix-and-prime), a simple binge-watching chrome extension, for auto skipping intro for [Netflix and Prime](http://bit.ly/aspn_pro).

If you want to discuss `match-rules`, comment below or reach out at [Twitter](https://twitter.com/naman03malhotra) or [LinkedIn](https://www.linkedin.com/in/naman03malhotra).