Building a React app with functional programming (Part 1)
Tags: react, functional, ramda, sanctuary
Published: 2021-01-13 15:27:06

---
title: Building a React app with functional programming (Part 1)
description: Applying functional programming patterns and data types in a real app.
published: true
tags: react,functionalprogramming,ramda,sanctuary
/cover_image: https://direct_url_to_image.jpg
---
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qfykaaaqmwbcmbtuqyvr.png)

*Originally published at https://www.markob.io/functional-programming-react/ on Jan 05 2021*

In this series we will scrap together a real world React app that allows the user to search for podcasts and audiobooks and add them to a feed that lists the latest episodes. 

It is intended to showcase how to use the functional programming concepts and patterns described in the fantastic [Professor Frisby's Mostly Adequate Guide to Functional Programming](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/content/).

We will explore how to compose functions, make the asynchronous look synchronous and leverage functional utilities from Ramda to write some succinct code.

You can skip the gibberish and grab the code from the [Github repo](https://github.com/marko911/adequate-guide-react), or tinker with it live in the [CodeSandbox](https://codesandbox.io/s/naughty-pine-00uxu?autoresize=1&fontsize=14&hidenavigation=1&moduleview=1&theme=dark).

### Core libraries

[Folktale Task](https://folktale.origamitower.com/api/v2.3.0/en/folktale.concurrency.task.html): for the `Task` data structure

[Sanctuary](https://sanctuary.js.org/): for `Either` and `Maybe` data structures.

[Most](https://github.com/cujojs/most): for `Stream` data structure (needed for consuming xml feed data)

[Ramda](https://ramdajs.com/docs/): for core lambda utilities like `map` `compose` `reduce` `identity`.

I like Ramda for this because their functions are not strictly curried, meaning we can pass all required arguments in any arity we like ie.

```js
const res = map(myFunc, myCollection)
// or
const res = map(myFunc)(myCollection)
```

### App requirements

#### Search page

The search page is where we can look for podcasts and books using a search field and render the results in a list.
From the rendered list of results, we can add / save the item locally so that the episodes of that subscription appear in our feed page.

- Call iTunes api for 2 different queries, `podcasts` and `audiobooks`
- Consolidate results into one list
- Allow user to `add` a result to their feed, creating a subscription for feed
- Save subscriptions to localStorage

#### Feed page

The feed page will simply display all the latest episodes from our subscriptions.

- Get all subscriptions from `localStorage`
- Create stream from calling each subscription's feed url
- Consolidate streams into one and render final list

## Search page - task and Either

#### iTunes API

Because our app depends on results from the iTunes API, we will create an `api` package first that
will make the calls.

Here is the first of 2 functions:

<p className="text-sm text-gray-500 file-caption">api.js</p>

```js
import axios from "axios"
import Task, { task } from "folktale/concurrency/task"
import { Left, Right } from "sanctuary-either"

const SEARCH_URL = "https://itunes.apple.com/search"

export const fetchMedia = ({ term, media, limit = 5 }) =>
  term
    ? task(async resolver => {
        try {
          const response = await axios.get(SEARCH_URL, {
            params: { term, media, limit },
          })

          resolver.resolve(Right(response))
        } catch (err) {
          resolver.resolve(Left(err))
        }
      })
    : Task.of(Left(null))
```

Our search function `fetchMedia` accepts 3 arguments:

- term: the search term,
- media: type of media ie podcast or audiobook,
- limit

and returns a [Folktale task](https://folktale.origamitower.com/api/v2.3.0/en/folktale.concurrency.task.html).

The `task` data structure is used as a container type for asynchronous results like promises. Using this data structure allows us to [containerize](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/content/ch08.html)
the api response and operate on it just like containerized values from synchronous operations.

`fetchMedia` returns a task which makes a network call and returns our result as an `Either` once this task is run. The `Either` allows us to handle an error the same as a successful response. `Right` is used to
transfer the actual value we are looking for, and `Left` signals that we didn't get what we were looking for and any operation on the result will be ignored
on a `Left`.

To summarize: our result is a `task` wrapping an `Either` which contains our actual api response value.

Now that we have our `api` package, let's build the search page.

Here is our React component:

<p className="text-sm text-gray-500 m-0 file-caption">Search.jsx</p>

```js
const err = err => {
  return []
}

const success = res => {
  return path(["data", "results"])(res)
}
const parseResponse = either(err)(success)

const [query, setQuery] = useState("")
const [results, setResults] = useState([])

const fetchBoth = async e => {
  e.preventDefault()
  const combineResults = curry((pods, audiobooks) => {
    return compose(flatten, map(parseResponse))([pods, audiobooks])
  })

  // directly taken from https://mostly-adequate.gitbooks.io/mostly-adequate-guide/content/ch10.html#ships-in-bottles
  const liftA2 = curry((g, f1, f2) => {
    return f1.map(g).ap(f2)
  })

  const allTasks = liftA2(
    combineResults,
    fetchMedia({ term: query, media: "podcast" }),
    fetchMedia({ term: query, media: "audiobook" })
  )

  allTasks.run().listen({
    onRejected: val => {
      console.error(val)
    },
    onResolved: setResults,
  })
}
```

So, a lot to unpack here but it is quite simple when its boiled down. I didn't include the html / jsx markup because we only need to know that `fetchBoth` is called once user types a search term and hits fetch button.

#### Consolidating api responses

Our search function `fetchBoth` consolidates both audiobooks and podcasts into the results listed on the page. This is a contrived example of having to make multiple api calls in order to get a single result list. 

Let's start with `allTasks`. This function lets us make two api calls in parallel and combine the results while keeping the values in
container land.

Breaking this down further, let's look at `liftA2`.

```js
const liftA2 = curry((g, f1, f2) => {
  return f1.map(g).ap(f2)
})
```

This is a curried function , meaning the arguments don't need to be supplied all the same time. They can be passed in one after the other at any point, and only once all arguments are provided does it run the function body. In our case, we passed everything in all at once.

Now, the `g` argument is function that we will apply to the result of the first function `f1`, in our case `g` is `combineResults` and `f1` is the result of `fetchMedia({ term: query, media: "podcast" })` which is a `task`. 

Since `task` is a functor, it has a `map` method and so our `liftA2` works.

```js
const combineResults = curry((pods, audiobooks) => {
  return compose(flatten, map(parseResponse))([pods, audiobooks])
})
```

Remember, once we map over a `task`, we are extracting the containerized value, so `f1.map(combineResults)` returns us a `task` but with the containerized value of `f1` passed into `combineResults`.

The containerized value is a `Left` or a `Right`, and `combinedResults` is waiting for the 2nd argument before it will run. `.ap` method of `task` allows us to apply the results of `f2` (which again will be a `Left`
or a `Right`) as the 2nd argument to `combineResults` and returns a task. In the end , we get to work with those containerized values without popping them out manually.

And since `combineResults` is a curried function, it gives us the ability to fire
off both api calls (the `fetchMedia` functions) in parallel without having to coordinate and wait for results of each and then pass them into `combineResults`.

So in the end, all we are doing is calling `combineResults` with the values from inside the two `tasks` (which are `Either`) returned by the `fetchMedia` functions.

Once both api calls return, the body of the `combineResults` function runs. Note: as you will see below, we must run the `task` that is returned
from `liftA2` in order to kick off the entire chain of events.

#### Parsing and data transformation with map, compose and either

```jsx
const err = err => {
  return []
}

const success = res => {
  return path(["data", "results"])(res)
}

const parseResponse = either(err)(success)

const combineResults = curry((pods, audiobooks) => {
  return compose(flatten, map(parseResponse))([pods, audiobooks])
})
```

Here we can showcase why an `Either` is useful and some Ramda goodies. In the implementation above, we simply grabbed the results of the calls and passed them to `parseResponse` which extracts the value. But we can also do something like transforming the data before we extract it. 

Our response has a `url` field and we want to <a target="_blank" href="https://ramdajs.com/docs/#toUpper">`toUpper` </a>it before parsing.

Just add that part to the <a target="_blank" href="https://ramdajs.com/docs/#toUpper"> `compose`</a> :

```jsx
const upperUrl = over(lensPath(["config", "url"]), toUpper)
const combineResults = curry((pods, audiobooks) => {
  return compose(
    flatten,
    map(parseResponse),
    map(map(upperUrl))
  )([pods, audiobooks])
})
```

The `upperUrl` function takes a response object , and applies the <a target="_blank" href="https://ramdajs.com/docs/#toUpper">`toUpper` </a> function to the "config.url" path of the object and returns a new object with the url uppercased.

We must <a target="_blank" href="https://ramdajs.com/docs/#map">`map` </a> twice because remember, first map will unwrap theunderlying value, the 2nd map operates on it. The beauty here is that if either `pods` or `audiobooks` returns a `Left`, we don't have to
have special case code for that, the Either takes care of it for us and the uppercase transformation simply doesn't run on a `Left`.

`compose`, `map`and`flatten`are all utilities from Ramda.<a target="_blank" href="https://ramdajs.com/docs/#compose">`compose`</a>takes a series of functions and returns a function that passes it's argument to the supplied list of functions from right to left. So we pass the array`[pods, audiobooks]`to`map(parseResponse)`and then`flatten`.

This is where we get to pop out our values from their containers and use them in the render. Some folks might even call this function from render to keep the impure part
at the render step.

`const parseResponse = either(err)(success)` uses `either` which is an import from `sancturay`.

This `either` function extracts the containerized value.
The first argument you supply, in our case the `err` function, is the result returned if the `Either` is a `Left`.

The second argument is returned if it is a `Right`. If the underlying `task` returns a `Left(err)`, we are ignoring the `err` argument and just returning an empty array. 

We can instead throw the error, `console.log` it , or pass it to a bug catch api for logging. If the `task` wraps a `Right` , then the `success` function runs with the argument as the
value the `Right` wraps.

As you can see, our `success` function takes the value the `Right` provides and returns the object path "data.results".
See [path from Ramda](https://ramdajs.com/docs/#path).

And the final part is that we `flatten` the results because we will have each api response as a list of results so it ends up a nested array.

In order to actually kickoff the task, we must run

```jsx
otherTasks.run().listen({
  onRejected: val => {
    console.error(val)
  },
  onResolved: setResults,
})
```

If you notice, we do not have any async/await logic inside our React component. The resolve/reject logic is whatever we specify in the `.listen()` method.

This ends our Search page, in the next part we tackle saving subscriptions and displaying a feed of latest episodes.