Locastic Feb 24

User Interface as a function of data

4 min read –
locastic function data blog

I came across the expression UI = f(data) while browsing the lit-HTML docs, and it really lighted a spark in my mind, it’s such a simple concept that can be used to describe what is it that we do as front-end developers.

This leads to a model that’s easy to write and easy to reason about: always try to describe your UI as a simple function of the data it depends on.

Lit.html, Polymer project

What I would like to do here is to breakdown this idea on a concrete example to further illustrate the point. I’ll be using React’s .tsx syntax for the examples but the idea of seeing code as a description of the data it depends on is what I want us to focus on – if you want to see the full code, you can find it on Github, and you can check out the finished demo on Netlify.

Let’s start by defining the data structure of a blog post.

Data and structure

interface IBlogPost {
    title: string,
    excerpt: string,
    image: {
        path: string
        title: string
        alt: string
    }
    featured?: boolean,
    published_at: Date
    tags?: Array<
        {
            title: string,
            path: string
        }
    >
}

We can then create a function that generates our UI BlogPost component from the data it depends on.

export default function BlogPost({ title, excerpt, image, featured, published_at, tags }: IBlogPost) {
    return (
        <article>
            <img src={image.path} alt={image.alt} title={image.title} />
            <h3>{title}</h3>
            <time>{published_at.toDateString()}</time>
            <p>{excerpt}</p>
        </article>
    )
}

This is the simplest possible scenario – our function gets the raw data and adds structure to it with semantic HTML tags, notice that we can also transform data where it’s needed as we did with the Date object here.

Let’s make it a bit more interesting, what would we do if we needed a way to make the featured blog posts stand out somehow?

Control flow

We can use conditions inside our component functions like this

<article className={(featured) ? 'featured' : ''}>

The above code will add a .featured CSS class for all posts that have the featured field set to true so we can apply the custom featured design with CSS.

Looping

To enrich our component a bit, we could loop through the post’s tags data and map each tag entry to an element that would represent it.

{tags && tags.map(tag => <a className="tag" href={tag.path}>{tag.title}</a>)}

We can also loop through multiple posts one level up the DOM tree inside a new BlogPostList component to render more blog posts.

{posts.map((post) => <BlogPost {...post} />)}

Filtering

If we only want to show featured posts, we can easily filter our data before using it

posts.filter(post => post.featured)

Props

By adding a boolean prop that gets passed into the component function once we initiate it, we can tell our component whether to show all posts or just the featured ones.

// App.tsx
<BlogPostList posts={posts} featuredOnly />

// BlogPostList.tsx
{posts
    .filter(post => featuredOnly ? post.featured : true)
    .map((post) => <BlogPost {...post} />)
}

We can use the same component to render all of the blog posts or just the featured ones depending on the context in which we want to use it.

If the condition gets passed it will be used to filter the results, if not it will display all of them.

The ability to use props like this opens a bunch of new possibilities for controlling our User Interface.

As a result we are getting a distinction here between the data we get from “outside”, from the API, while the context data we define ourselves when using our functional UI. We can look at them both as different possible arguments of our function.

State

There is also a third type of data that we can use inside our components and its called state.

const [featuredOnlyState, setFeaturedOnlyState] = useState(false);
  return (
    <>
      <label htmlFor="featured_filter">
        <input
          id="featured_filter"
          type="checkbox"
          onChange={() =>
            setFeaturedOnlyState(!featuredOnlyState)
          } />
          Featured only
      </label>
      <BlogPostList posts={posts} featuredOnly={featuredOnlyState} />
    </>

With state, we can have data that is changing at runtime and the functions that depend on that state data get rerendered to reflect the new conditions.

State data can be both internal like in the example above, or it could be external as, for example, a news feed on a social network with fresh data coming from the API in realtime.

Wrap up

In conclusion, we could say that the major part of the job of a frontend developer is to create and use functions that take input arguments from:

  • the API data
  • context-specific options (props)
  • application runtime state

then use basic programming capabilities to create the final representation of the UI.

  • semantic structuring
  • data transformations
  • conditional rendering
  • looping through data
  • data filtering

I hope that the key take away from this post would be for you to try to see past the obvious and notice the underlying data in your app or a piece of User Interface.

You can try to figure out what is the data structure behind the UI and what kind of transformations of that data would be needed to output the UI that you are seeing. The idea is that this method can be really beneficial in lowering the cognitive load of reasoning about how to structure a project therefore it gives us a straightforward model to both understand and write our UI.

I would really love to hear your thoughts on this, does this model help you see UI in a new way and do you think that doing so would help you in your day to day work?