Kevin J. Kelly

Building a Resume with React

December 10, 2019 - 5 min read

How my resume was created

I've used the JSON Resume tool for the last handful years to manage my resume, its a document generator, running on the notion of a common JSON schema for resume content. If you are looking for a tool to quickly structure a resume it's worth a look. Given some JSON and a selected theme, you can quickly export a resume to PDF or HTML.

I was in the process of building this website to host my resume and hobby projects and decided to take my resume and recreated it in React. I'd like to say there was a flashy reason to do this. Pragmatically, the resume has nothing to gain in performance or content editability (since it's just updating a JSON file). Hobby projects are a great chance for developers to throw needed buy-in aside and try new things "just because".

I've had a laundry list of things in the javascript ecosystem to revisit, two of which were:

  1. publishing NPM packages
  2. leveraging component library tooling

With that, resume-react was created, and storybook powered the living docs. Onward to a journey of how the componentry of react-resume came to be.

Desired Output

  1. The resume renders in a "print-like" format on desktop.
  2. The resume renders in a readable format on mobile.
  3. The resume can be printed from the browser (and saved to PDF).

This is a two part blog post. Here we will explore migrating an existing HTML resume over to react. Part two explores print formatting considerations.

The Approach

I had at one point in time forked the onepage theme from JSON Resume for some minor style tweaks, so I thankfully had an existing product to work from.

An excerpt of my old JSON Resume

I find often when working against a mockup it helps to carve out your React componentry with a top-down approach. Reusable code can be found through itterating through a design from the top-down, so to begin I envisioned creating a monolithic resume component.


Iteration 1: 1 Component to Rule them all

A monolithic Resume component

Pros

  • One file.
  • Migration of HTML/CSS from existing JSON Resume would be fast

Cons

  • Low reusability
  • High code complexity

The pros here are file simplicity, I could copy the existing HTML into 1 react component and call it a day, should I need to revisit the resume in the future all I'll need to do is open a resume.js file and modify my component.

The number of properties applied onto the resume component will be high. That is not of great concern to me, as I view this component as serving a single purpose.

<Resume
  name="John Smith"
  title="Architect"
  email="john.smith@example.com" 
  phone="(555) 555-5555"
  location={{city: "New York", region: "NY"}} 
  phone="555-555-5555"
  summary="Lorem ipsum dolor sit amet  ..."
  experience={[
    {
      name: "Acme Inc",
      position: "Lead Architect",
      startDate: "2002-01-10",
      endDate: "2008-08-01"
      highlights: [
        "Designed a 30 story house boat.", 
        "Scuba instructor on company retreat.",
        "Weaved many underwater baskets."
      ]

    }
  ]}
  volunteer={[/*...*/]}
  education={[/*...*/]}
  skills={[/*...*/]}
/>

This lacks code reusability. If I want to create a modified resume layout my options are to either:

  1. Copy and paste the Resume component code. Bad: duplicated logic
  2. Add the modified layout to the Resume component and add a prop to select the modified layout. Bad: breaks single responsibility principle

It's not evident yet, but there's an abundance of functionality within this one component. Having a top-level component, Resume, sounds beneficial! It's the interfacing layer of the JSONResume data into the presentation layer. If my goal was to migrate my resume into a React component, I could call it a day. With reacts composition ideology, I have encapsulated my resume behind a component barrier. Wherever it is rendered, other components rendered shouldn't care about it!

Rather than just pick apart what lives under the shell of Resume, I'll give my code the "test smell-test".


Iteration 2: Now with Smell-O-Vision

Test Smell-Test

What could be tested?

Break down what you could test in your component. This is awkward since code already exists, and tests do not for the react component in this instance. If this list is long, try and find some commonalities of your tests. This may shed some light on a reusable component to break out.

  1. Name is shown.
  2. Title is shown.
  3. Address is shown.
  4. Email is shown.
  5. Phone number is shown.
  6. "SUMMARY" category title is shown.
  7. Summary content is shown.
  8. "EXPERIENCE" category title is shown.
  9. Each job: company is shown.
  10. Each job: title is shown.
  11. Each job: timeframe is shown.
  12. Each job: each achievement is shown.
  13. "VOLUNTEER" category title is shown.
  14. Each volunteer: company is shown.
  15. Each volunteer: title is shown.
  16. Each volunteer: timeframe is shown.
  17. Each volunteer: each achievement is shown.
  18. "EDUCATION" category title is shown.
  19. Each education: school is shown.
  20. Each education: degree is shown.
  21. Each education: timeframe is shown.
  22. Each education: GPA is shown.
  23. "SKILLS" category title is shown.
  24. Each skill: skill group is shown.
  25. Each skill group: individual skills are shown.

Reading through my test cases, I see a pattern arise between each section of the resume below the header:

  1. A category title is shown.
  2. Some content is shown.

I could potentially break my Resume component down to use a Content component. This would reduce the duplication of logic within my Resume that would be applied to each content section.


Content Component

EXPERIENCE
Section details go here, including things like:
  • organization info
  • timeframe
  • list of details
<Content title="EXPERIENCE">
  <div>
    <span>Section details go here, including things like:</span>
    <ul>
      <li> organization info </li>
      <li> timeframe </li>
      <li> list of details </li>
    </ul>
  </div>
</Content>

Content commonalities

I could go further to say within the content there are additional correlations, like how the (work) "EXPERIENCE" section and "VOLUNTEER" section have matching test criterias and the style of each content section is identical.

Looks like I can use a single component to handle both sections. Out of fear of pre-mature optimization, I'm going to create a component for each content section, but let the "VOLUNTEER" section component call the "EXPERIENCE" section component. For generic purposes, I'll call that component Work.


Work Component

Acme Inc, Lead Architect
Jan 2002 to Aug 2008

  • Designed a 30 story house boat.
  • Scuba instructor on company retreat.
  • Weaved many underwater baskets.
<Work 
  name="Acme Inc" 
  position="Lead Architect" 
  startDate="2002-01-10" 
  endDate="2008-08-01" 
  url="https://example.com"
  highlights={["Designed a 30 story house boat.", "Scuba instructor on company retreat.", "Weaved many underwater baskets."]}
/>

The "EDUCATION" section seems to closely match "EXPERIENCE" as well. I see that my resume's education section includes a school, thats like a company (in data labeling form, I'm not knocking our education system), but the GPA render doesn't correlate to the achievement render. The "EXPERIENCE" achievements are a list for each job, but I have only one GPA for each "EDUCATION" degree. Slightly lost? Me too. This looks like premature optimization. When I hit a point where an extraction isn't crystal-clear I back off. It looks like "EDUCATION" and "SKILLS" for now will be their own components.


Education Component

State Tech, Bachelor of Science - Underwater Basket Weaving
Sep 1990 to May 1996

GPA: 3.4

<Education 
  institution="State Tech" 
  studyType="Bachelor of Science" 
  area="Underwater Basket Weaving"
  startDate="1990-09-10" 
  endDate="1996-05-01" 
  gpa= "3.4"
/>

Skills Component

Weaving (Master): Straw, Yarn, Paper, The fabric of time and space
<Skills 
  name="Weaving" 
  level="Master"
  keywords={["Straw","Yarn","Paper","The fabric of time and space"]}
/>

Current State

Rome wasn't built in a day, and the cleanest code wasn't built in one teration. Extracting code often poses new challenges, so I err on the side of caution. Extract too much? You're now dealing with component hell. Extract too little? You're not getting "clean cuts" of your components for reusability.

At this point I'm happy with the reduction in code duplication, by structuring each section of my resume into Content blocks containing Work, Volunteer, Education, and Skill components.

EXPERIENCE
Acme Inc, Lead Architect
Jan 2002 to Aug 2008

  • Designed a 30 story house boat.
  • Scuba instructor on company retreat.
  • Weaved many underwater baskets.
Example LLC, Architect
Jul 2008 to Jan 2000

  • Made examples
  • Generated stock footage
  • Lorel ipsum
EDUCATION
State Tech, Bachelor of Science - Underwater Basket Weaving
Sep 1990 to May 1996

GPA: 3.4

SKILLS
Weaving (Master): Straw, Yarn, Paper, The fabric of time and space
Design (Intermediate): CSS, Figma
<Content title="EXPERIENCE">
  <Work 
    name="Acme Inc" 
    position="Lead Architect" 
    startDate="2002-01-10" 
    endDate="2008-08-01" 
    url="https://example.com"
    highlights={["Designed a 30 story house boat.", "Scuba instructor on company retreat.", "Weaved many underwater baskets."]}
  />
  <Work 
    name="Example LLC" 
    position="Architect" 
    startDate="2008-07-01" 
    endDate="2000-01-01" 
    url="https://example.com"
    highlights={["Made examples", "Generated stock footage", "Lorem ipsum"]}
  />
</Content>
<Content title="EDUCATION">
  <Education 
    institution="State Tech" 
    studyType="Bachelor of Science" 
    area="Underwater Basket Weaving"
    startDate="1990-09-10" 
    endDate="1996-05-01" 
    gpa="3.4"
  />
</Content>
<Content title="SKILLS">
  <Skills 
    name="Weaving" 
    level="Master"
    keywords={["Straw","Yarn","Paper","The fabric of time and space"]}
  />
  <Skills 
    name="Design" 
    level="Intermediate"
    keywords={["CSS","Figma"]}
  />
</Content>

What remains? My resume contains a chunk of unique logic that pertains to showing my name, title, contact details, that may be worth componentizing. The summary is just a text blurb, so it can use a Content block, no need for extra components.


Heading Component

Moving the heading logic into its own component, Heading, isn't saving any lines of code. If anything it's adding more files to the project to manage. For future reusability sake I want to make the heading its own component, I may place it elsewhere on my projects as my signature, and it would be silly to import from my resume to gain a shared lower-level component.

<Heading 
  name="John Smith" 
  title="Architect" 
  email="john.smith@example.com" 
  location={{city: "New York", region: "NY"}} 
  phone="555-555-5555"
/>  

End Structure

At this point, the building blocks exist to make a resume. The resume-react component library stays fairly unopinionated on styling. I opted for that approach to make adoption easier for users.

SUMMARY
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
EXPERIENCE
Acme Inc, Lead Architect
Jan 2002 to Aug 2008

  • Designed a 30 story house boat.
  • Scuba instructor on company retreat.
  • Weaved many underwater baskets.
Example LLC, Architect
Jul 2008 to Jan 2000

  • Made examples
  • Generated stock footage
  • Lorem ipsum
EDUCATION
State Tech, Bachelor of Science - Underwater Basket Weaving
Sep 1990 to May 1996

GPA: 3.4

SKILLS
Weaving (Master): Straw, Yarn, Paper, The fabric of time and space
Design (Intermediate): CSS, Figma
<Heading 
    name="John Smith" 
    title="Architect" 
    email="john.smith@example.com" 
    location={{city: "New York", region: "NY"}} 
    phone="555-555-5555"
  />
<Content title="SUMMARY">
  <span> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </span>
</Content>
<Content title="EXPERIENCE">
  <Work 
    name="Acme Inc" 
    position="Lead Architect" 
    startDate="2002-01-10" 
    endDate="2008-08-01" 
    url="https://example.com"
    highlights={["Designed a 30 story house boat.", "Scuba instructor on company retreat.", "Weaved many underwater baskets."]}
  />
  <Work 
    name="Example LLC" 
    position="Architect" 
    startDate="2008-07-01" 
    endDate="2000-01-01" 
    url="https://example.com"
    highlights={["Made examples", "Generated stock footage", "Lorem ipsum"]}
  />
</Content>
<Content title="EDUCATION">
  <Education 
    institution="State Tech" 
    studyType="Bachelor of Science" 
    area="Underwater Basket Weaving"
    startDate="1990-09-10" 
    endDate="1996-05-01" 
    gpa= "3.4"
  />
</Content>
<Content title="SKILLS">
  <Skills 
    name="Weaving" 
    level="Master"
    keywords={["Straw","Yarn","Paper","The fabric of time and space"]}
  />
  <Skills 
    name="Design" 
    level="Intermediate"
    keywords={["CSS","Figma"]}
  />
</Content>

Closing Thoughts

Without diving into the component definitions, the componentry has been established to adhere to the JSONSchema. the Heading, Work, Volunteer, Education, and Skills accept properties matching the schema structure. For example, this snippet shows how I put the components to use on my resume:

import resumeData from './sampleResume.json';

//...

<Heading {...resumeData.basics} />
<Content title="SUMMARY">
  <span> {resumeData.basics.summary}</span>
</Content>
<Content title="EXPERIENCE">
  {resumeData.work.map(entry => (
    <Work {...entry} key={`${entry.name}-${entry.startDate}`} />
  ))}
</Content>
<Content title="VOLUNTEER">
  {resumeData.volunteer.map(entry => (
    <Volunteer {...entry} key={`${entry.name}-${entry.startDate}`} />
  ))}
</Content>
<Content title="EDUCATION">
  {resumeData.education.map(entry => (
    <Education {...entry} key={`${entry.name}-${entry.startDate}`} />
  ))}
</Content>
<Content title="SKILLS">
  {resumeData.skills.map(entry => (
    <Skills {...entry} key={`${entry.name}-${entry.level}`} />
  ))}
</Content>

//...