The page will showcase all of our current and previous experience. We will implement this similar to the about page. We will create a Job model, a JSON file that contains our job data and a JobController. We will then create 2 React components, Jobs and Job. React is useful in this situation since we can reuse the Job component.


NOTE: Always play it safe when entering company logos and job descriptions. If you’re unsure, always ask your company for permission to use their logo on your site. Don’t enter confidential data about the company in your description. If you wouldn’t put it on LinkedIn, don’t put it here.

Now let’s get to coding.

  1. In the Models folder, create a new class called Job.cs. Add the following data.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace PersonalResume.Models
{
    public class Job
    {
        public int Id { get; set; }
        public string CompanyName { get; set; }
        public string Logo { get; set; }
        public string JobTitle { get; set; }
        public string StartDate { get; set; }
        public string EndDate { get; set; }
        public string Location { get; set; }
        public string[] JobDuties { get; set; }
    }
}

2. Add your company logos to the src>images>jobs folder. See note above about company logos. NOTE: To load images faster, compress your image online using tinypng.com, or imagecompressor.com.

3. Add jobs.json file to your Data folder and update the data to reflect your employment history. Start with your most recent/preset job first.

[
  {
    "Id": 1,
    "CompanyName": "Test Company",
    "Logo": "test-company-logo.jpg",
    "JobTitle": "Software Engineer",
    "StartDate": "Jan 2018",
    "EndDate": "Present",
    "Location": "Baltimore, MD",
    "JobDuties": [
      "Develop applications in C# (.NET), SQL Server and Angular JS",
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
    ]
  },
  {
    "Id": 2,
    "CompanyName": "Test Company",
    "Logo": "test-company-logo.jpg",
    "JobTitle": "QA Tester",
    "StartDate": "June 2017",
    "EndDate": "Dec 2017",
    "Location": "Baltimore, MD",
    "JobDuties": [
      "Design test plans, scenarios, scripts, and procedures.",
      "Work with developers to test specific use cases on Web or Mobile",
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
    ]
  }
]

3. Add a new Controller called JobsController.cs, choose the Api Controller – empty template. Add the following code. This is similar to how we are retrieving the about data, except we are returning a list of objects and not just one object. Run the project and test the endpoint in the browser at /api/jobs.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using PersonalResume.Models;

namespace PersonalResume.Controllers
{
    [Route("api/jobs")]
    [ApiController]
    public class JobsController : ControllerBase
    {
        public IEnumerable<Job> GetJobs()
        {
            string jsonText = System.IO.File.ReadAllText("./Data/jobs.json");
            return JsonConvert.DeserializeObject<IEnumerable<Job>>(jsonText);
        }
    }
}

4. Create a new component called Jobs.js, and paste the following code. This component retrieve the list of jobs from our JSON file, loop through these jobs, and display a new Job component. The job object will get passed into the Job component as a prop. This is when react becomes useful, the ability to reuse components. We have also added a spinner as a class. NOTE: Spinner from reactstrap did not work, so I’m using the class directly from bootstrap. The Spinner will show while the jobs are being retrieved from the back-end.

import React, { Component } from 'react';
import {
    Row, Col
} from 'reactstrap';
import Job from './subcomponents/Job';

export class Jobs extends Component {
    displayName = Jobs.name

    //Constructor method
    //We update the state object with the properties from our List of Jobs. This will be an empty array for now.
    //we pull data from the API
    //isLoading is set to true and will be set to false once the data comes back from API
    constructor(props) {
        super(props);
        this.state = {
            jobs: [],
            isLoading: true
        }

    }

    //Calls the JobsController endpoint /api/jobs when the component is mounted in the DOM.
    //isLoading is set to false since the data is loaded.
    componentDidMount() {
        fetch('api/jobs')
            .then(response => response.json())
            .then(data => {
                this.setState({ jobs: data, isLoading: false })
            })
    }

    //Adds a spinner that indicates to the user that Jobs are loading. 
    //else clause will loop through the list of jobs and render a Job component from the subcomponents folder. 
    render() {
        if (this.state.isLoading) {
            return (<div className=" spinner-border image-center" style={{ width: '5rem', height: '5rem' }}> <div/>{' '}</div>);
        }
        else {
            return (
                <div>
                    <Row>
                        {this.state.jobs.map(job =>
                            <Col md={6} key={job.id}>
                                <Job job={job} />
                            </Col>
                        )}
                    </Row>
                </div>
            );
        }

    }
};

5. Add the Job component. This will render a bootstrap card with the company image, job title, job location, dates, and job description. This will be a functional component that takes in a props parameter from the parent component Jobs. props are variables that come from the parent component and can be used if they don’t change.

import React, { Component } from 'react';
import {
    Row, Col, Card, CardImg, CardText, CardBody,
    CardTitle, CardSubtitle, Button
} from 'reactstrap';
import './Job.css';

//props are properties that come from a parent component, in this case Jobs. props will not change, so we can use them directly in our render function
function Job(props) {

    const job = props.job;

    return (
        <div className="job">
            <Card className="job-card">
                <CardBody>
                    <Row>
                        <Col md={7} sm={12}>
                            <CardImg className="company-logo center-mobile smaller-width" top width="100%" src={require('../../images/jobs/' + job.logo)} />
                        </Col>
                        <Col md={5} sm={12}>
                            <div className="center-job-info">
                                <CardTitle className="h4">{job.companyName}</CardTitle>
                                <CardSubtitle className="job-title">{job.jobTitle}</CardSubtitle>
                                <CardText>{job.startDate} – {job.endDate}</CardText>
                                <CardText className="job-location">{job.location}</CardText>
                            </div>
                        </Col>
                    </Row>
                    <h5 className="text-center">Job Duties</h5>
                    <ol>
                        {job.jobDuties.map(duty =>
                            <li key={duty}>{duty}</li>
                        )}
                    </ol>
                </CardBody>
            </Card>
        </div>
    );
}

export default Job;

6. Add a new css file in the subcomponents folder called Job.css. This is for styling the card components (Job.js).

@media only screen and (min-width: 800px) {
    .company-logo {
        width: 50%;
        float: left;
    }
}

@media only screen and (max-width: 800px) {
    .center-job-info {
        text-align: center;
        padding-bottom: 10px;
    }

    .company-logo {
        margin-bottom: -30px;
    }
}

.job-title {
    font-weight: bold;
}

.job-location {
    margin-top: -15px;
}

.job-card {
    min-height: 525px;
}

.job {
    padding-top: 20px
}

7. Before we run our app, we have a make a few updates to the react-router and navmenu. Go to App.js and make the following update. The Route component will determine what component to load depending on the URL. We are going to load Jobs at / and at /Jobs.

import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { Jobs } from './components/Jobs';

import './custom.css'

export default class App extends Component {
  static displayName = App.name;

  render () {
    return (
      <Layout>
        <Route exact path='/' component={Jobs} />
        <Route path='/jobs' component={Jobs} />
        <Route path='/counter' component={Counter} />
        <Route path='/fetch-data' component={FetchData} />
      </Layout>
    );
  }
}

8. In Navmenu.js, Make the following updates. This will add Jobs to the Nav menu at the top.

import React, { Component } from 'react';
import { Collapse, Container, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import './NavMenu.css';
import { Jobs } from './Jobs';

export class NavMenu extends Component {
  static displayName = NavMenu.name;

  constructor (props) {
    super(props);

    this.toggleNavbar = this.toggleNavbar.bind(this);
    this.state = {
      collapsed: true
    };
  }

  toggleNavbar () {
    this.setState({
      collapsed: !this.state.collapsed
    });
  }

  render () {
    return (
      <header>
        <Navbar className="navbar-expand-sm navbar-toggleable-sm ng-white border-bottom box-shadow mb-3" light>
          <Container>
            <NavbarBrand tag={Link} to="/">PersonalResume</NavbarBrand>
            <NavbarToggler onClick={this.toggleNavbar} className="mr-2" />
            <Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={!this.state.collapsed} navbar>
              <ul className="navbar-nav flex-grow">
                <NavItem>
                  <NavLink tag={Link} className="text-dark" to="/jobs">Jobs</NavLink>
                </NavItem>
                <NavItem>
                  <NavLink tag={Link} className="text-dark" to="/counter">Counter</NavLink>
                </NavItem>
                <NavItem>
                  <NavLink tag={Link} className="text-dark" to="/fetch-data">Fetch data</NavLink>
                </NavItem>
              </ul>
            </Collapse>
          </Container>
        </Navbar>
      </header>
    );
  }
}

9. Update custom.css in the Client>src folder.

@media only screen and (min-width: 768px) {
    .school-logo {
        width: 150px;
        float: right;
    }
}

@media (max-width: 767px) {
    /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */
    body {
        padding-top: 50px;
    }

    .center-mobile-text {
        text-align: center;
    }

    .smaller-width {
        width: 50%;
    }
}


@media only screen and (max-width: 767px) and (orientation: portrait) {
    .center-mobile {
        margin-left: auto;
        margin-right: auto;
        display: block;
    }

    .smaller-width {
        width: 50%;
    }

    center-mobile-text {
        text-align: center;
    }
}

.image-center {
    width: 50%;
    display: block;
    margin-left: auto;
    margin-right: auto;
    padding-top: 5px;
}


.card {
    transition: box-shadow .3s;
    border-radius: 10px;
    border: 1px solid #ccc;
    background: #fff;
}

.card:hover {
    box-shadow: 0 0 15px rgba(33,33,33,.2);
}

Click here to see updated code.