DEV Community

Cover image for My personal page with React
Oswaldo Diaz
Oswaldo Diaz

Posted on • Updated on

My personal page with React

I've been playing for a while with React, and my experience has been pretty satisfactory so far, such, that I decided to create my own personal page and now I want to show you how you can do it.

The whole app is on github.

Prerequisites

First let's assume you have all your environment setup for React:

I use yarn instead of npm, but is not necessary, if you rather use npm replace in commands yarn add for npm install.

yarn add -g create-react-app

Creating a new React app

To create a new React app you just need to run create-react-app followed by your app's name.

create-react-app my-page
cd my-page

Now a basic project is created with the following structure:
Initial code structure

Let's run our app to see what we have so far.

yarn start

Your browser will open on http://localhost:3000 and you'll see React logo spinning around.

Initial app

Dependencies

We'll use a set of dependencies to render easily our components:

  • Bootstrap: an open source toolkit for developing with HTML, CSS, and JS.
yarn add bootstrap --save
yarn add jquery --save
yarn add popper.js --save
yarn add reactstrap --save
  • Fontawesome: For vector icons and social logos on your website, the web’s most popular icon set and toolkit.
yarn add @fortawesome/react-fontawesome --save
yarn add @fortawesome/fontawesome-svg-core --save
yarn add @fortawesome/free-brands-svg-icons --save
  • Moment: Parse, validate, manipulate, and display dates and times in JavaScript.
yarn add moment --save

Let's also import bootstrap in index.js

import 'bootstrap/dist/css/bootstrap.min.css';

Run App

Maintain the app running so you can see your change on real time.

yarn start

Components

The magic of React is to handle fragments of your page as components, where each component works independently, that way the code can be replace, relocate or refactor easily without the need of spike the whole app. For this project we will have 3 main components: top bar, profile and footer where each component has a specific subject to render, and each of them can later be divide in other components.

Let's add a folder called components to our src, and let's create our 3 components:

mkdir src/components
touch src/components/TopBar.js
touch src/components/Profile.js
touch src/components/Footer.js

The following will be the code of component TopBar.js.

import React from 'react';

class TopBar extends React.Component {
  render() {
    return <div>This is the top bar</div>;
  }
}

export default TopBar;

First, we're creating a component called TopBar that extends from React Component, this class has a unique method called render() and it needs to return an HTML tag, in this case a simple <div>This is the top bar</div>. At the end we need to export this class so we can use it in others components. Replicate the same code for Profile.js and Footer.js.

Now, let's modify App.js so it renders our new components:

import React, { Component } from 'react';
import './App.css';
import TopBar from './components/TopBar';
import Profile from './components/Profile';
import Footer from './components/Footer';

class App extends Component {
  render() {
    return (
      <div className="App">
        <TopBar />
        <Profile />
        <Footer />
      </div>
    );
  }
}

export default App;

Now we're able to see the following:

Initial page with components

Top Bar

Top bar will consist of a navigation bar with the user's name and 1 link to Profile. Let's replace our code on TopBar.js.

import React from 'react';
import {Container, Collapse, Navbar, NavbarToggler, NavbarBrand, Nav, NavItem, NavLink } from 'reactstrap';

class TopBar extends React.Component {
  constructor(props) {
    super(props);

    this.toggle = this.toggle.bind(this);
    this.state = {
      isOpen: false
    };
  }

  toggle() {
    this.setState({
      isOpen: !this.state.isOpen
    });
  }

  render() {
    return (
      <Navbar color="dark" dark expand="md">
        <Container>
          <NavbarBrand href="/">
            <span>Oswaldo Díaz</span>
          </NavbarBrand>
          <NavbarToggler onClick={this.toggle}/>
          <Collapse isOpen={this.state.isOpen} navbar>
            <Nav className="ml-auto" navbar>
              <NavItem>
                <NavLink href="/profile/">Profile</NavLink>
              </NavItem>
            </Nav>
          </Collapse>
        </Container>
      </Navbar>
    );
  }
}

export default TopBar;

Now our design has a proper top bar.

App with top bar

Footer

For the footer we will use FontAwesome to render Github and Linkedin icons with links to each profile. Now let's replace our Footer.js content.

import React from 'react';
import {Container} from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faLinkedin, faGithub } from '@fortawesome/free-brands-svg-icons'

class Footer extends React.Component {
  render() {
    return (
      <footer>
        <hr/>
        <Container>
          <a href="https://www.linkedin.com/in/oswaldo-d%C3%ADaz-397b036b/"><FontAwesomeIcon icon={faLinkedin} /></a>
          <a href="https://github.com/oswaldodiaz"><FontAwesomeIcon icon={faGithub}  /></a>
        </Container>
      </footer>
    )
  }
}

export default Footer;

With the code above, 2 icons will appear at the end with links to our linkedin and github profiles.

App with footer

Profile

For the profile let's divide content in 2 sections, first a summary of you and then a tab with more detailed information. The second section will render new components Experience.js and Education.js.

Before we move on, let's create a JSON file where we can store all or professional experience and then we will render this information using proper components:

touch src/profile.json

And add the following content to profile.json.

{
  "title": "Software Engineer",
  "summary": "I'm passionate about Computer Software development, looking always for different and most efficient solutions to daily problems of clients. Always growing as a person and as a co worker, looking always to give my best for the company I'm working with.",
  "studies": [
    {
      "institute": "Universidad Central de Venezuela",
      "logo": "https://media.licdn.com/dms/image/C560BAQEqH4pTb_C3Vg/company-logo_400_400/0?e=1547078400&v=beta&t=w7dqsi-UJEkF4ChiuDRTSUPQ0H_hZiJ6NwWvix_b1Uc",
      "url": "http://www.ciens.ucv.ve/",
      "title": "Bachelor on Computer Science",
      "durationInYears": "5",
      "graduationYear": 2013
    }
  ],
  "certifications": [
    {
      "logo": "https://www.certmetrics.com/api/ob/image/amazon/c/1",
      "url": "https://aws.amazon.com/certification/certified-solutions-architect-associate/",
      "title": "AWS Certified Solutions Architect - Associate",
      "issuer": "Amazon Web Services",
      "issueDate": "2018-09-01",
      "expiryDate": "2020-09-01",
      "verificationLink": "https://www.certmetrics.com/amazon/public/badge.aspx?i=1&t=c&d=2018-09-13&ci=AWS00487431"
    },
    {
      "logo": "https://acclaim-production-app.s3.amazonaws.com/images/86629924-6c10-442c-8742-05ff5e45e922/Oracle-Certification-badge_OC-Associate.png",
      "url": "https://education.oracle.com/es/oracle-certified-associate-java-se-8-programmer/trackp_333",
      "title": "Oracle Certified Associate, Java SE 8 Programmer",
      "issuer": "Oracle",
      "issueDate": "2016-05-01",
      "verificationLink": "https://www.youracclaim.com/badges/aa466aec-ddbc-4e67-8038-aa8466a4aef9/linked_in_profile"
    }
  ],
  "experiences": [
    {
      "companyName": "ServiceRocket",
      "logo": "https://media.licdn.com/dms/image/C560BAQE0UGAq3qc4PA/company-logo_200_200/0?e=1547078400&v=beta&t=wPEtCDSDDI8HHn779fD3yG5tr95YQC6pe71f81HU7oQ",
      "url": "https://www.servicerocket.com/",
      "roles": [
        {
          "title": "Software developer",
          "description": "Development of Application (bots, add-ons) that help simplify day-to-day work for companies using platforms such as Workplace or Atlassian products. To achieve this purpose, Serverless framework is being used taking advantage of AWS stack technologies.",
          "startDate": "2018-01-01",
          "currentJob": true,
          "location": "Santiago Province, Chile"
        },
        {
          "title": "Agile Java Developer",
          "description": "Own the processes of squad, develop code, develop user stories, estimates and other attachments, collaboratively with Product Owner through formal and informal meetings, manage the risks of committed backlog items, manage the quality of processes, output, and throughput.",
          "startDate": "2016-07-01",
          "endDate": "2018-01-01",
          "location": "Santiago Province, Chile"
        }
      ]
    },
    {
      "companyName": "Alaya Digital Solutions",
      "logo": "https://media.licdn.com/dms/image/C4D0BAQEOGmtbMe-jiA/company-logo_400_400/0?e=1547078400&v=beta&t=O-BNIwr7tSojDadQq7WHlBT349-M2WEl7tgY4IJxLzU",
      "url": "http://www.alaya.cl/",
      "roles": [
        {
          "title": "Project Lead",
          "description": "In charge of planning, take decissions about the technology to used and manage projects with a software development team with abilities to build web applications. This job position requires to have knowledges of the technology used such as Java 8, Spring 4, Hibernate, J2EE, Oracle DB, Weblogic and be able to write the code if neccessary.",
          "startDate": "2016-03-01",
          "endDate": "2016-07-01",
          "location": "Santiago Province, Chile"
        },
        {
          "title": "Software Engineer",
          "description": "In charge of building applications for public entities working with backend (J2EE, Srping, Hibernate, OracleDB) and frontend (ExtJs) through REST web services. Responsible of applying good design patterns allowing the increment of software easier and faster on each iteration.",
          "startDate": "2015-10-01",
          "endDate": "2016-03-01",
          "location": "Santiago Province, Chile"
        }
      ]
    },
    {
      "companyName": "Synergy-GB",
      "logo": "https://media.licdn.com/dms/image/C4E0BAQG8mmQH36m-Xg/company-logo_400_400/0?e=1547078400&v=beta&t=ijgqvtMLx6iNypHtW_7WySNju7rTP7Tvo3ujAchGvck",
      "url": "http://www.alaya.cl/",
      "roles": [
        {
          "title": "Project Lead",
          "description": "Project engineer with interest on design and develop of software components responsible for managing the logic and systems integration, that allow communicate web and mobile applications with the data sources of a business. With experiences developing REST/SOAP web services in Java and design and modeling of data for SQL and No-SQL databases.",
          "startDate": "2014-04-01",
          "endDate": "2015-10-01",
          "location": "Caracas, Venezuela"
        }
      ]
    },
    {
      "companyName": "IBM",
      "logo": "https://media.licdn.com/dms/image/C560BAQEZL5_LD7kuhg/company-logo_400_400/0?e=1547078400&v=beta&t=kD5vayFgrDcbzzpbNYhpfPPF0kr-pQWAnuHdl0j7mRU",
      "url": "https://www.ibm.com/ve-es/",
      "roles": [
        {
          "title": "Sap Consultant",
          "description": "ABAP and Portal Consultant with interest of developing ABAP programs that can be use at a high level for other departments and also with abilities to install and migrate a Portal SAP system.",
          "startDate": "2013-06-01",
          "endDate": "2014-03-01",
          "location": "Caracas, Venezuela"
        }
      ]
    },
    {
      "companyName": "4Geeks",
      "logo": "https://media.licdn.com/dms/image/C4D0BAQERvzTMXtxd7g/company-logo_400_400/0?e=1547078400&v=beta&t=I_dafILp03Xn8WaI9-9IBHah7Z5wPpW55D8WY1kgR6c",
      "url": "https://www.4geeksacademy.co",
      "roles": [
        {
          "title": "Web programmer",
          "description": "In charge of developing web services using the framework Django used for a e-commerce web site",
          "startDate": "2012-11-01",
          "endDate": "2013-03-01",
          "location": "Caracas, Venezuela"
        }
      ]
    }
  ]
}

File profile.json has general information (title and summary) and sections studies, certification and experience that we'll render later using components.

Let's create 2 new components:

  • Experience.js: to render job experience.
  • Education.js: to render education (studies and certifications).
touch src/components/Experience.js
touch src/components/Education.js

First, for Experience.js component let's use Media to render our job experience.

import React from "react";
import { Container, Row, Col } from "reactstrap";
import profile from "../profile";
import moment from "moment";
import { Media } from "reactstrap";

function getDuration(duration) {
  const years = parseInt(duration / 12);
  const months = (duration > 12)? duration % 12 : duration
  return (years > 0? years + " year" + (years > 1? "s": "") + " and " : "") + (months > 0? months + " month" + (months > 1? "s": "") : "");
};

class Experience extends React.Component {
  render() {
    return <Container>
      <Row>
        <Col>
          {profile.experiences.map(function (experience, i) {
            moment.locale('en');

            const totalDuration = experience.roles.reduce(function (cnt, role) {
              const startDate = moment(role.startDate);
              const timeEnd = moment(role.currentJob ? new Date() : new Date(role.endDate));
              const duration = moment.duration(timeEnd.diff(startDate));
              return Number(cnt) + Number(duration.asMonths().toPrecision(1));
            }, 0);

            return (
              <div key={i}>
                <Media>
                  <Media left top href={experience.url}>
                    <Media object src={experience.logo} alt={experience.companyName}/>
                  </Media>
                  <Media body>
                    <Media heading>
                      <a href={experience.url}>{experience.companyName}</a>
                      <span className="jobTotalDuration">{getDuration(totalDuration)}</span>
                    </Media>

                    {experience.roles.map(function (role, i) {
                      const startDate = moment(role.startDate);
                      const timeEnd = moment(role.currentJob ? new Date() : new Date(role.endDate));
                      const duration = Number(moment.duration(timeEnd.diff(startDate)).asMonths().toPrecision(1));

                      return <div key={i}>
                        <h5>{role.title}</h5>
                        <span
                          className="jobDuration">{startDate.format('MMM YYYY')} - {role.currentJob ? 'Present' : timeEnd.format('MMM YYYY')} ({getDuration(duration)})</span>
                        <span className="jobLocation">{role.location}</span>
                        <p className="jobDescription">{role.description}</p>
                      </div>
                    })}
                  </Media>
                </Media>
              </div>
            );
          })}
        </Col>
      </Row>
    </Container>
  }
}

export default Experience;

We're importing the JSON we created some steps above through: import profile from "../profile";.

In this component we're defining a function that it's been used to clean up the code and abstract that logic from the component.

Another thing we're adding is map to iterate over a collection of experience and on each experience (and over a collection of roles as well).

Now, let's work on Education.js. We'll be using Media as well.

import React from 'react';
import {Container, Row, Col, Media} from 'reactstrap';
import moment from 'moment';
import profile from '../profile.json';

class Education extends React.Component {
  render() {
    return <Container>
      <Row>
        <Col>
          {profile.studies.map(function (study, i) {
            return <Media key={i}>
              <Media left top href={study.url}>
                <Media object src={study.logo} alt={study.institute}/>
              </Media>
              <Media body>
                <Media heading>
                  <a href={study.url}>{study.institute}</a>
                </Media>
                {
                  [
                    {
                      "key": "Title",
                      "value": study.title
                    },
                    {
                      "key": "Graduation Year",
                      "value": study.graduationYear
                    },
                    {
                      "key": "Duration",
                      "value": study.durationInYears + " year(s)"
                    }
                  ].map(function (object, i) {
                    return <div>
                      <Row>
                        <Col className="formLabel">{object.key}:</Col>
                      </Row>
                      <Row>
                        <Col>{object.value}</Col>
                      </Row>
                    </div>
                  })
                }
              </Media>
            </Media>
          })}
        </Col>
      </Row>
      <br/>
      <br/>
      <Row>
        <Col>
          <h4>Certifications:</h4>
          <hr/>
          {profile.certifications.map(function (certification, i) {
            const verification = certification.verificationLink ?
              <Row>
                <Col>
                  <a className="certificateLink" href={certification.verificationLink}>See certificate</a>
                </Col>
              </Row> : "";
            return <Media key={i}>
              <Media left top href={certification.url}>
                <Media object src={certification.logo} alt={certification.title}/>
              </Media>
              <Media body>
                <Media heading>
                  <a href={certification.url}>{certification.title}</a>
                </Media>
                <Row>
                  <Col>{moment(certification.issueDate).format('MMM YYYY')} - {(certification.expiryDate) ? moment(certification.expiryDate).format('MMM YYYY') : 'Present'}</Col>
                </Row>
                <Row>
                  <Col>{certification.issuer}</Col>
                </Row>
                {verification}
              </Media>
            </Media>
          })}
        </Col>
      </Row>
    </Container>
  }
}

export default Education;

Now that we have our 2 sections defined, let's modify Profile.js.

import React from 'react';
import {Jumbotron, Container, TabContent, TabPane, Nav, NavItem, NavLink } from "reactstrap";
import classnames from 'classnames';
import Experience from "./Experience";
import Education from './Education'
import profile from '../profile.json'

class Profile extends React.Component {
  constructor(props) {
    super(props);

    this.toggle = this.toggle.bind(this);
    this.state = {
      activeTab: '1'
    };
  }

  toggle(tab) {
    if (this.state.activeTab !== tab) {
      this.setState({
        activeTab: tab
      });
    }
  }

  render() {
    return <div>
      <Jumbotron>
        <Container>
          <h1 className="display-3">{profile.title}</h1>
          <p className="lead">{profile.summary}</p>
        </Container>
      </Jumbotron>

      <Container>
        <Nav tabs>
          <NavItem>
            <NavLink className={classnames({ active: this.state.activeTab === '1' })}
                     onClick={() => { this.toggle('1'); }}>
              Experience
            </NavLink>
          </NavItem>
          <NavItem>
            <NavLink className={classnames({ active: this.state.activeTab === '2' })}
                     onClick={() => { this.toggle('2'); }}>
              Education
            </NavLink>
          </NavItem>
        </Nav>
        <TabContent activeTab={this.state.activeTab}>
          <TabPane tabId="1">
            <Experience/>
          </TabPane>
          <TabPane tabId="2">
            <Education/>
          </TabPane>
        </TabContent>
      </Container>
    </div>;
  }
}

export default Profile;

First, we're using a Jumbotron for the summary and Tabs for Experience and Education.

Addding a profile picture

Look for your profile pic and save it under src folder with name profilePic.jpg (it could be another name).

Let's modify TopBar.js to add a profile pic:

...
import profilePic from '../profilePic.jpg';

class TopBar extends React.Component {
  ...

  render() {
    return (
      <Navbar color="dark" dark expand="md">
        <Container>
          <NavbarBrand href="/">
            <img src={profilePic} className="profile-pic" alt="Oswaldo Díaz"/>
            ...

Adding style

Finally, let's add some style by modify App.css.

.profile-pic {
  height: 4vmin;
  margin-right: 10px;
  border-radius: 50px;
}

a {
  color: #343a40;
}

a:hover {
  color: #173798;
  text-decoration: none;
}

div.media {
  margin-top: 30px;
  margin-bottom: 10px;
}

div.media .media-heading .jobTotalDuration {
  color: #666;
  font-size: 14px;
  display: block;
}

div.media img {
  height: 10vmin;
  margin-right: 10px;
  border-radius: 50px;
}

div.media .jobDuration {
  color: #444;
  display: block;
}

div.media .jobLocation {
  color: #666;
}

.formLabel {
  font-weight: bold;
}

.certificateLink {
  color: cornflowerblue;
}

footer {
  margin-top: 20px;
  margin-bottom: 10px;
  border-top: #444444;
  text-align: center;
}

footer a {
  color: black;
  font-size: 30px;
  margin-right: 10px;
  margin-left: 10px;
}

Final result

Experience
Experience Tab

Education
Education Tab

This is my current page hosted in AWS as a S3 static website under url http://oswaldodiaz.info.

Let me know if you find this tutorial useful, if something was not clear enough or if you have experience with React what would you do different ;)

Top comments (11)

Collapse
 
joshtynjala profile image
Josh Tynjala

For a more static site, consider checking out Gatsby. It's still based on React, so the development experience will be similar to what you have here. However, your content that you deploy will come pre-rendered, so users will see it faster, and it's more search engine friendly out of the box.

Collapse
 
oswaldodiaz profile image
Oswaldo Diaz

Good tip :) I'll take a look at Gatsby

Collapse
 
thorstenhirsch profile image
Thorsten Hirsch

When calling "yarn add" you don't need the "--save" flag. Yarn has no such flag.

Collapse
 
oswaldodiaz profile image
Oswaldo Diaz

I didn’t know that, thank you!

Collapse
 
tofl profile image
Tom F.

Thank you very much. I intend to learn React this month, so I think I'll read this little guide. Just a question: if I use the Express framework for NodeJS (in order to create a dynamic website), how does combining the two (React + Express) work ?

Collapse
 
oswaldodiaz profile image
Oswaldo Diaz

I haven't work with Express before, but if I'm not mistaken you'll provide an API with express, and will consume your API from React. That's the way I'm integrating React currently but I'm using AWS Lambda functions to provide an API.

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited

That's all well and good, but why on earth would you build such a page with React? It makes absolutely zero sense

Collapse
 
oswaldodiaz profile image
Oswaldo Diaz

I’m learning react and I wanted to create my personal page so it seems like a good first project to work on. But I’m curious, what do you think it’ll be more appropriate for a page like this one?

Collapse
 
jonrandy profile image
Jon Randy 🎖️

Since the page features almost no interactive elements (therefore requiring little, if any JS) - server side rendering (with caching) would seem a far better solution. The size of the payload being delivered to the client would be greatly reduced, pages would load faster on slow connections, and the pages would be far more accessible across the board (screen readers, web scrapers, any and every search engine, etc.). The site would also still work if the JS failed to load for whatever reason.

Using React on the client side for such a site seems a totally inappropriate application of technology

Thread Thread
 
oswaldodiaz profile image
Oswaldo Diaz

It makes a lot of sense! I'll take that in consideration for the future, thank you for your input :)

Collapse
 
rifaimartin profile image
Rifai Martin

Do You Have Number whatsApp ?