Building a Modern React Web Application — The IdeaDog Front-End

Brennan D Baraban
8 min readJun 26, 2019

--

Before I knew I wanted to build IdeaDog, a social media web application for sharing ideas, I knew I wanted to build a full-fledged React app. And not just any React app, but a modern React app.

I had been independently learning JavaScript/TypeScript, React and Node.js over the previous two months, and was eager to put my skills to the test with a React application built entirely from scratch. Ever obsessed with coding while maintaining best practices and using the latest technologies, I insisted on keeping the React techniques and libraries I used as modern as possible.

Alright, alright, I know. I keep repeating it, but what exactly do I mean when I say that IdeaDog is a modern React app?

Well, I would simply love to show you.

HANDMADE, WITH LOVE

When I stated that I made IdeaDog from scratch — I meant it! I truly wanted to build IdeaDog from the ground up. No create-react-app, no Next.js (although I do want to do a project with Next), no insert-your-favorite-React-starter-kit -here— my own configuration, my own scripts.

This meant writing my own package.json:

My own tsconfig.json :

And of course, using a Node.js package manager — my preference is yarn.

Finally, I spent a fair amount of time researching module bundlers. Webpack dominates this field, but I am turned off by hefty, complicated boilerplate, of which Webpack is, well, dependent on.

In came Parcel.js, which bills itself as a zero-configuration bundler.

I love Parcel.js. I am not going to turn this article into a love-letter to this awesome new module bundler, but I’d like to briefly walk through what I had to do to configure Parcel in IdeaDog.

1. Install it

yarn global add parcel-bundler

2. Add one line to my tsconfig.json

{
...,
"jsx": "react",
...
}

3. Define two scripts in my package.json

{
...,
"scripts": {
"build": "parcel build src/index.html",
"start": "parcel src/index.html",
...,
},
...
}

…and that’s it! Just like that, Parcel enables hot-module-reloading and production build minimizing. Plus, if I ever get around to my dream goal of compiling some of the front-end functionality to Web Assembly, Parcel will enable me import the binary seamlessly.

DESIGNED, WITH HELP

Although I like to think otherwise, I am a developer, not a designer. I do not have a design background, and I recognize that I am not well-versed in proper web design. For this reason, I truly appreciate the resources made freely available by Material Design.

Source: https://material.io/design/

I came to Material-UI for its extensive supply of React components; I stayed for the organization’s equally extensive resources on implementing good design practices and standards.

Additionally, I was quite fortunate to have committed to using Material-UI just a few days after the team released version 4.0

More than just an updating of its components, Material-UI v4 involved a complete rewrite of its documentation, written for TypeScript first, and exemplifying of its components with React hooks. Which brings me to my next highlight…

HOOKS, HOOKS, HOOKS

Say what now? IdeaDog features zero traditional React class components.

🎣🎣🎣🎣🎣

Hooks are cool, hooks are awesome, hooks are the future; once I became familiar with hooks, I insisted on using them everywhere.

One particularly cool use-case of hooks was in my IdeaGrid component, a React element wrapping the scrolling column of ideas, the central aspect of IdeaDog’s user interface.

When I first integrated this component with the filtering features implemented on the back-end, I experienced an issue where ideas would not re-render. Back in the “olden” days, fixing this bug would have involved implementing some combination of shouldComponentUpdate and componentDidUpdate (I’m guessing, I never bothered with those functions! 😉).

With React’s useEffect hook, however, fixing this behavior became relatively straightforward. Using useEffect, I made the currently-displayed ideas a local state array dependent on a received prop ideas containing the new ideas to display.

// Current ideas, tracks changes based on received ideas prop                         const [, setCurrentIdeas] = React.useState<Idea[]>([]);          React.useEffect((): void => {
setCurrentIdeas(ideas);
}, [ideas]);

I ended up using this technique multiple times, additionally using it for updating tags select menus and forcing the upvote/downvote logos to re-render properly within idea cards.

…OK. I have to admit. There’s one exception to my zero-class-component statement. My ErrorBoundary component.

But, this is only because React has not yet implemented static getDerivedStateFromError for hooks! I promise!

LIST VIRTUALIZATION

Remember that idea grid GIF I just showed? I’ll post it again.

I’m limited by my GIF program’s seven-second limit, but that list will scroll forever and ever… well, until there are no more ideas to show.

I’ll always remember the first time I pulled up IdeaDog with live data fetched from the back-end. I had been building the user interface statically, so of course it was nice and snappy. Then, I tested querying ideas from the back-end — receiving all 900+ dummy ideas at once — and had to sit there for a good half-a-minute before the ideas even rendered, let alone became functional.

It became immediately clear that I needed to dig into some React rendering efficiencies for the application to work.

I tested combinations of five— yes, five— list virtualization libraries before I came across React Virtual List. Virtualizing idea cards proved to be a challenge, because the cards were not of static height — ideas with longer text increased the height of the card, and for responsiveness on smaller screens I altered the layout of the cards entirely.

GitHub: https://github.com/dwqs/react-virtual-list

React Virtual List renders sizes of list items dynamically, which was exactly what I needed. In the long run, however, I can foresee benefits of writing my own list virtualization program, so that I can improve the user experience even more with skeleton cards and better size estimation.

Who knew displaying a ton of data at once was so difficult! (Facebook, Twitter, Reddit, pretty much any and all social media products, and yeah, OK, pretty much just everyone did).

ASYNCHRONOUS, DECLARATIVE DATA FETCHING

I love this tweet from “The React Guy” himself, Dan Abramov.

Before I began building IdeaDog, I anticipated needing a state management library (read, Redux). Halfway through development of IdeaDog, I anticipated needing a state management library. Heck, having it nearly complete, I anticipated needing to improve my program with a state management library.

I never needed to implement a state management library.

Did I go out of my way to avoid implementing Redux? Well, yeah. You know how I mentioned that I’m turned off by hefty, complicated boilerplate code? I really don’t like hefty, complicated boilerplate code, and Redux, is well — déjà vu— dependent on it.

So, IdeaDog is a pretty complex web application. How did I pull this off without Redux?

HOOKS

Well, first off, I’ve said it before and I’ll say it again — hooks are awesome.

🎣🎣🎣🎣🎣

Hooks truly made me comfortable working with state, and for the most part, I was seamlessly able to pass state down and back up as props among components.

NAVI

On top of insisting on hooks, I discovered a fantastic new routing library, Navi.

The content behind IdeaDog is asynchronous. URL routes on the site directly correspond to the ideas, tags, and users I fetch from the back-end API, through calls which happen on page load.

I conceived the above functionality before discovering Navi; hence, you might be able to guess that I was pretty thrilled to come across a new routing library enabling nigh exactly what I was trying to achieve.

Navi works by mapping routes to views (think, app renders). This, declarative routing is nothing special in and of itself, of course — libraries like React Router have been there, done that. The catch with Navi is that declarative routing can be achieved asynchronously; in other words, route mapping can go hand-in-hand with data fetching.

An overview of the routing for IdeaDog’s home page gives a good idea of how I used Navi to handle routing and data fetching throughout the application.

Before I render anything at all on a page load, I first make asynchronous calls to fetch data. This includes an array of ideas, which are fetched based on the sort and tags parameters pulled from the URL, as well as the current logged in user, which might or might not be relevant depending on particular cookies. Additionally, you see that I fetch all available idea tags and set an array of currently-selected tags.

Now, I have everything I need for properly rendering the given page, and I can pass the fetched data across my React components as needed.

Pretty sweet!

As you can see at the bottom, Navi permits seamless code-splitting — when rendering the /:sort route, I lazily load the other two routes implemented in IdeaDog, /idea and /user. Each of those routes features their own system of data fetching and passing.

This setup might seem too simple, and the cool thing is that — it is! Nonetheless, there’s one catch (there’s always a catch). The data is still asynchronous, so before we render anything, we must ensure we have all of our data fetched. Fortunately, this is easy to take care of with React’s new Suspense feature.

The Suspense ensures that the page will not crash if required data is still in the process of being fetched. If you interact with the site, you may notice a flashing loading bar at the top of each page as it loads — this was a simple feature (in retrospect; nothing is ever simple at first) made easy with Navi’s integration with Suspense.

RESPONSIVE

Finally, this entire piece would be meaningless if IdeaDog was not fully responsive on mobile. I’m happy to say — IdeaDog is mobile-friendly.

So, please, pull up IdeaDog on your phone and share some awesome ideas!

What do you mean, there’s more to web applications than just the front-end?

I kid. You’re absolutely right. While I spearheaded the front-end of IdeaDog, there is of course much more work that went into this project. Fortunately, we have resources for you to read more on all that work!

Oh wait, and of course — visit our site!!

ideadog.site

github.com/bdbaraban/ideadog

--

--