Building From Zero: Thinking About Your JavaScript Application Before Data

Alley
8 min readMar 18, 2021

by Jo Murgel

A silhouette of a building crane and three construction workers against a pale yellow sky

Demo Link

At Alley, we do a lot of work with data. Whether it measures entrepreneurial trends in the US, visualizing state health data, or informing the public of correlations in education and workforce data, the applications we build are continually digesting new data from countless sources and challenged with clearly presenting that data.

Ideally, a project would begin with a full dataset. However, clients don’t always have control over when the data arrives or may often want to publicly share data as it’s collected rather than after everything has become available. In that case, how do you create a JavaScript application for data that doesn’t yet exist? The size and shape of the data you are working on influences how your app consumes, stores, and interacts with said data. It informs the structure of your application framework down to individual components.

If you have an incomplete picture, take the information you do have and build to accommodate the information you don’t; in other words, build from zero. You might think of this in a similar way you would test-driven development in which you would build a test to fail first, then build your function to pass that test. Here’s a detailed look at the development side of this process.

The Data

Even if a complete dataset is not yet available, it would be impossible to work without at least some understanding of your expected outcome. You should have some rough estimations of the final size and either assign or inherit a structure to form a small and temporary picture to kick off development. You might know that you’ll be getting data from a combination of public government APIs, though our source may be unknown at this time, and you have data that is shaped like this, all in one array:

[
{
content: {…},
data: [],
id: 5689,
slug: ‘mollis-sagittis-sed-eget-laoreet’,
title: ‘Mollis sagittis sed eget laoreet’,
type: ‘single’,
},
{
id: 459009,
data: [],
slug: ‘elementum-malesuda-vel-nostra-secelerisque’,
title: ‘Elementum malesuada vel nostra scelerisque’,
type: ‘set’,
},
{
content: {…},
id: 4599,
slug: ‘facibus-vulutpat-sed’,
title: ‘Faucibus volutpat sed’,
type: ‘page’,
}
]

You could surmise from this sample data set that there would be three views: a single datum, a data set, and a page. If this is all the client provides, you could make two safe assumptions. First, this represents the type of data but is a small fraction of the data’s size. Second, this means at least three distinct views that you will need to consider.

You could map out a plan to start building our app that handles this data with five total views to create:

  • a homepage
  • a single datum view
  • a dataset view
  • a page view
  • a fallback (traditionally a 404 or catch-all view)

Given that you have these views and would expect a large amount of data, it makes sense that you could use React Router to handle routing along with something like Redux Saga for data management. In this example, I’ll be using React.js as our app framework. Developers may be apt to underestimate the final size of the data and build something less robust. With a more firm picture of the outcome, it’s possible to use built-in React Hooks or roll with a custom solution. A word of caution: if years of experience has taught me anything, it’s that the expectation and the reality very rarely align. Always assume a worst-case scenario to avoid more significant hardship down the road.

Before I talk about data, let’s talk about the app.

App Structure

I’ll assume that you and your clients want to keep things moving. Let’s set up a straightforward React app with React Router and start on some routing. Rather than jumping in and trying to wire up everything from the start, let’s take a step back and make sure you have our fallbacks in place. I’ve set our default route to match anything, our NoMatch component, which is our 404/No Found/Catch all and should be responsible for any circumstances in which you don’t have a set view for our data.

import React from "react";
import { Link, Route, Switch } from "react-router-dom";
import "./styles.css";

// Components.
import NoMatch from "./components/NoMatch";

export default function App() {
return (
<div className="App">
<nav>
<Link to="/">Home</Link>
<Link to="/set/mollis-sagittis-sed-eget-laoreet">Data Set</Link>
<Link to="/page">Page</Link>
<Link to="/set/datum/5689">Datum</Link>
</nav>
<hr />
<Switch>
<Route path="*" component={NoMatch} />
</Switch>
</div>
);
}

Our NoMatch component looks like this:

// Dependencies.
import React from "react";

const NoMatch = () => <div>This is our 404 component.</div>;

export default NoMatch;

It’s essential to understand why I’ve asked you to start here. It’s easy to get hung up on where the data is coming from, how you’ll be injecting it into our app, or what the design or interactions may look like, the cart before the horse, as the saying goes. But before you get to any of that, you should understand what the app does when it fails.

Applications expect specific data sets. Still, when that data changes, the app could break, which is not a bad practice, but I press for doing as much work upfront as possible to prep your app for inevitable change, so the work required when you finally get your “real world” data, you have fewer hurdles to jump over.

Building and Testing Routes

Right now, our app does nothing. You have a simple linking structure, a React Router setup, and a component to handle those Routes, but that’s it.

Let’s do a little cleanup and create a new Page component, which will do a few things.

  1. Render a page component.
  2. Check to make sure that you have valid page data.
  3. Handle redirects if you don’t.

Those updates might look like this. You’ll add a route for a page, which should have a page slug.

<Route exact path=”/:slug” component={Page} />

Our Page component might look like this:

// Dependencies.
import React from "react";

const Page = () => <div>This is a Page component.</div>;

export default Page;

Once added, you should see that your home, set, and datum links will route to the NoMatch component, and our page route /page now renders the Page component. Great!

If you look at my mock data, one data object has the type of page and the slug provided is facibus-vulutpat-sed. The goal here would be to verify that you’re displaying a page for which there is content and redirect to our NoMatch component should there be missing data.

You should now add a simple check to our component via the useEffect hook and the React Router hook useParams to get our :slug.

// Dependencies.
import React from "react";
import { Redirect, useParams } from "react-router-dom";

// Store.
import store from "../store";

const Page = () => {
// Get slug from url params.
const { slug = "" } = useParams();
// Redirect if I don't have valid data.
if (!store.some((obj) => obj.type === "page" && obj.slug === slug)) {
return <Redirect exact from={`/${slug}`} to="/404" />;
}
return <div>This is a page component.</div>;
};

export default Page;

And update our routes to include a /404 route, which provides a specific location to direct invalid routes. It’d fall above the /:slug route since the checks trickle down.

<Route exact path="/404" component={NoMatch} />
<Route exact path="/:slug" component={Page} />
<Route path="*" component={NoMatch} />

The above may look like a significant change, but what I’ve done is to check the slug that is read from the URL params against the data that you have in the store for a match. Otherwise, just redirecting our NoMatch component now /404 URL. If I’ve done my job right, you shouldn’t need our catch-all route * because all routes were accounted for ahead of time.

Extending The App

Let’s add the rest of our “valid” routes and build out the remainder of the components. Our Page component will guide our DataSet and Datum component.

The Datum component is different from the Page component because the route relies on the id rather than the slug.

// Dependencies.
import React from "react";
import { Redirect, useParams } from "react-router-dom";

// Store.
import store from "../store";

const Datum = () => {
// Get slug from url params.
const { id = "" } = useParams();
// Redirect if I don't have valid data.
if (
!store.some((obj) => obj.type === "single" && obj.id === parseInt(id, 10))
) {
return <Redirect exact from={`/datum/${id}`} to="/404" />;
}
return <div>This is a datum component.</div>;
};

export default Datum;

However, the result is the same. The DataSet component would be similar to the Page component.

I’ll also add a Home component to clean things up, the contents of which are identical to our Page component without any logic to support routing.

Wrapping Up

All said and done, you have six total routes: two that handle our NoMatch 404-ing, and one for each of the potential views discussed at the top.

<Route exact path="/" component={Home} />
<Route exact path="/set/:slug" component={DataSet} />
<Route exact path="/set/datum/:id" component={Datum} />
<Route exact path="/404" component={NoMatch} />
<Route exact path="/:slug" component={Page} />
<Route path="*" component={NoMatch} />

Given our initial data set of a single datum /set/datum/5689, a data set /set/elementum-malesuda-vel-nostra-secelerisque, a page /facibus-vulutpat-sed, and our homepage /. Anything that doesn’t fit these route structures: /datum/5689 for example, will route to our NoMatch view.

You should never hit anything other than the components set up (with a valid slug or id) or the 404 pages regardless of what you type into the URL bar, and that’s half the battle.

What’s Next?

You could do more. Design and need would change the structure of these route components, and the build would get more complicated. You could utilize a Provider from Redux to pass the store down to the app and start to think about how you’ll get the data and where that will be stored, and how performance is affected. Whatever you do, the app is ready for it, having used a sample data set directly and accounting for nothing first and working backward like UX development building mobile-first. You’re working with React and Building from Zero.

Building the app in this way can create a structure into which data can make continual improvements easier to handle than building to what you think you’ll have upfront. This is only a first step, but it lets us focus on the broad strokes first while providing rapid response when the project finally launches.

Would you like to learn more about our development techniques and how to maintain consistent stellar quality even in the face of large unknowns? Reach out to us on Twitter, or subscribe to our email updates at the bottom of this page for more frequent dives into design, development, and more!

--

--

Alley

We are strategists, researchers, designers, and developers who craft digital experiences for publishers, nonprofit institutions, museums, and brands. alley.co