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:
- Make sure you have a recent version of Node.js installed.
- Follow the installation instructions for Create React App to make a new project.
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:
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.
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:
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.
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.
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
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)
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.
Good tip :) I'll take a look at Gatsby
When calling "yarn add" you don't need the "--save" flag. Yarn has no such flag.
I didn’t know that, thank you!
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 ?
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.
That's all well and good, but why on earth would you build such a page with React? It makes absolutely zero sense
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?
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
It makes a lot of sense! I'll take that in consideration for the future, thank you for your input :)
Do You Have Number whatsApp ?