Add pattern matching in React using Daggy

Lift off

I’m going to talk about a great library called Daggy, which gives you the ability to add pattern matching to your Javascript code and transform the React render method.

This article is for people who are already familiar with React and have experience working with it. If you’re a newbie in React, read the article about a quick start in React and then return to us. Have a nice read!

The problem of “if”

Let’s start with this statement: we love React 😊
React is a powerful and easy-to-use library with many advantages. And, of course, it has some trade-offs. One of them: it’s a library… so we don’t have any predeclared patterns and restrictions—a.k.a. standards and practices. In this case, we have to invent our own solutions.

One of these patterns is called conditional rendering. What does this mean? This “if” hell:

render() {
if (this.state.isLoading) {
    return ...
}

if (this.state.isError) {
    return ...
}

if (this.state.isListEmpty) {
    return ...
}

return this.state.list.map(item => ...)
}

I don’t like it… do you? I think that you’re just used to it.

OK, maybe it’s necessary to solve this problem. I think this is the case. Why? Here are some reasons:

  1. React is based on the idea of a declarative way of defining the render function. Using ifs, we have to investigate all the imperative workflow instead of just taking a look at the JSX (or pug) code.
  2. You have to define a lot of system helper fields in the state, like isLoading, isError, etc. And it’s complicated to reuse such solutions, even with Redux.

The Solution: javascript pattern matching

Once upon a time, I got the solution: pattern matching. There is only one problem with this solution: we don’t have one in the JS standard, and so we need to use a custom solution. I found one: Daggy.

Daggy is a library that gives you the ability to define a type and values of this type—sum types. Using pattern matching we can declare the action depending on the value of this type. Take a look at this example:

const Item = daggy.tagged('Item', ['title'])

const List = daggy.taggedSum('List', {
  Empty: [],
  Items: [Item],
})

const list = List.Empty

list.cata({
  Empty: () => console.log(‘empty…’),
  Items: items => items.map(item => console.log(item.title)),
})

If you have some functional background you may say, “Oh my Haskell!”
And it almost is 🤘

The advantages of this approach are:

  1. You get the type security to make your code more predictable.
  2. Ability to reuse this definition over your code.
  3. You make your app behavior more obvious and your life easier 🙂

And a small explanation about types from my perspective.

Binding with Haskell and FP

Types make our code more expressive and readable. You define a range of values that a variable with a type as a label can be or a function can accept. It protects you from summing strings or compelling a cat to “woof”.

Another advantage is that it makes your code more descriptive:

type Password = String

This gives you a little bit of context, but I’d recommend that you take a look at Haskell to get familiar with the modern functional hype 😉

Javascript pattern matching example

Well, let’s get back to the React world and sprinkle your app with the magic of types. How can it help us make our code more readable and declarative? I think the best way to answer this question is to create a pet application with poor names and logic, but that is to the point 😀

I’d recommend using create-react-app and then yarn add daggy. You can use whatever project structure you want, and I’d recommend the following:

src
  index.js
  App.js
  App.css
  types.js

Our pet application will be a small list with fake data fetching and filtering. We’re going to start by declaring types—it’s pretty easy:

const Item = daggy.tagged('Item', ['title'])

const List = daggy.taggedSum('Page', {
  Empty: [],
  Initial: [],
  Items: [Item],
  NotFound: ['searchMessage'],
  FetchError: [],
})

A small explanation. The tagged function gives you a type-wrapper called Item with a field called “title”. The taggedSum function returns union types so that we can work with an entity depending on the state of the value.

We’ll define start data and the fake fetch function:

const LIST = [
  { title: 'Butter' },
  { title: 'Bread' },
  { title: 'Eggs' },
  { title: 'Fish' },
  { title: 'Cake :3' },
]

const petFetch = () =>
  Promise
    .resolve(LIST)
    .then(list => ({ list }))

Then we’ll focus on our component. State and render go first:

class App extends Component {
  state = {
    list: List.Initial,
    searchString: '',
  }

  render() {
    return (
      <div className="container">
        <ul>
          {this.state.list.cata({
            Empty: () => <li>This list is empty =(</li>,
            Initial: () => <li>Loading...</li>,
            Items: items => items.map(({ title }) => <li>{title}</li>),
            NotFound: seacrhMessage => <li>There is nothing on your request: ’{seacrhMessage}’</li>,
            FetchError: () => <li>Oooooops...</li>,
          })}
        </ul>
      </div>
    );
  }
}

Take a closer look at the render function. We are using the cata method to start the pattern matching and, depending on the value of the list, we’ll render a piece of JSX. Personally, I think that looks better than ifs or even switch 💩

The result is the following:

taggedSum Daggy

Easy peasy lemon squeezy. Eloquent UI 🙃
And now we’re ready to add other methods:

componentWillMount() {
    setTimeout(this.fetchList, 2000)
  }

  fetchList = () =>
    petFetch()
      .then(res => this.wrapList(res.list))
      .catch(() => this.setState({ list: List.FetchError }))

  wrapList = (list) => {
    const wrapperList = list.length === 0
      ? List.Empty
      : List.Items(list)

    this.setState({ list: wrapperList })
  }

Here we start fetching with a 2-second delay to show the loading method. Everything’s clear with the lifecycle method componentWillMount and fetchList, but you might have some questions about the wrapList method. Here we construct a value using type constructors, depending on the length of the list, and then set it to the state field “list”.

For the sake of good code design, I’d recommend that you separate wrapping and state changing, then create a set of functions for working with types. I’ll skip it here, to prevent creating a post with tremendous size 😜

Let’s make our life a little bit harder and add a filter for titles. First, we should upgrade our state and render method.

 state = {
     list: List.Initial,
     searchString: '', }

 render() {
     return (
       <div className="container">
         <input onChange={this.handleChange} />
         <ul>
           {this.filterList().cata({
             Empty: () => <li>This list is empty =(</li>,
             Initial: () => <li>Loading...</li>,
             Items: items => items.map(({ title }) => <li key={title}>{title}</li>),
             NotFound: seacrhMessage => <li>There is nothing on your request: {seacrhMessage}</li>,
             FetchError: () => <li>Oooooops...</li>,
           })}
         </ul>
       </div>
     );
 }

Then we need to add a method for filtering, handling the input.

filterList = () =>
    this.state.list.cata({
      Empty: () => List.Empty,
      Initial: () => List.Initial,
      Items: items => {
        const filteredList = items.filter(this.matchSearch)

        return filteredList.length > 0
          ? List.Items(filteredList)
          : List.NotFound(this.state.searchString)
      },
      NotFound: () => List.NotFound(this.state.searchString),
      FetchError: () => List.FetchError,
    })

  handleChange = ({ target }) =>
    this.setState(() => ({ searchString: target.value }))

  matchSearch = item =>
    (item.title.indexOf(this.state.searchString) !== -1)

The workflow of the filtering is pretty clear and obvious: we filter the list and the result of this action wraps into the type constructors. We get value depending on the result of filtering. Here’s what we got:

filtering Daggy

Create tagged constructors with Daggy

Here we go. First of all, I’d like to say about the library Daggy that it’s a nice and easy-to-use portal to the functional world that gives us the ability to use pattern matching and union types in JS applications. Second, please go to the official Daggy GitHub of this library and click on “Star”, so that the creators will know you find their tool useful 😎

Also, types give us a simple way to make our code readable, giving entities of our application a descriptive name and a list of fields for predictability.

I’d recommend that you get an experience in any Functional Language to be on top of the modern style of JS coding and write more maintainable applications, not through the objects, but through functions and types.

As always, have a nice experience with your Functional start!

Yulia Garanok

Yulia Garanok

Marketing Manager

From our blog

Stay up to date

Check out our newsletter

© 2024 Red Panda Technology (dba datarockets). All Rights Reserved.