A React-based Blog Example

Views: 6822
Wrote on May 6, 2020, 4:51 p.m.

>> CODE on GitHub <<.

Only a few easy steps you can build a simple article blog with ReactJS

Index and APP

index.js

import 'whatwg-fetch';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

serviceWorker.unregister();

index.css

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

App.js

import React, {Component} from 'react';
import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom';
import HomePage from "./pages/HomePage";
import AboutPage from "./pages/AboutPage";
import ArticleListPage from "./pages/ArticleListPage";
import ArticlePage from "./pages/ArticlePage";
import NavBar from "./navbar";
import NotFoundPage from "./pages/NotFoundPage";
import './App.css';

class App extends Component {
  render() {
    return (
      <Router>

        <div className="App">
          <NavBar/>
          <div id="page-body">
            <Switch>
              <Route path="/" component={HomePage} exact />
              <Route path="/about" component={AboutPage} />
              <Route path="/articles-list" component={ArticleListPage} />
              <Route path="/article/:name" component={ArticlePage} />
              <Route component={NotFoundPage} />
            </Switch>
          </div>
        </div>

      </Router>
    );
  }

}

export default App;

App.css

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  font-size: 18px;
}

button {
  padding: 16px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  font-size: 16px;
  background-color: black;
  color: white;
  border-radius: 8px;
  cursor: pointer;
}

h3, h4 {
  margin-bottom: 0;
}

.logoimg{
  height:60px;
  display: block;
  margin-left: auto;
  margin-right: auto;
}

nav {
  border-bottom: 4px solid black;
  text-align: center;
  position: fixed;
  top: 0;
  width: 100%;
  background: white;
}

nav ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

nav li {
  display: inline-block;
  padding-top: 16px;
  padding-bottom: 16px;
}

nav a {
  text-decoration: none;
  color: black;
  font-weight: bold;
  padding: 16px 32px;
}

nav a:hover {
  background: black;
  color: white;
}

#page-body {
  max-width: 700px;
  margin: auto;
  padding-top: 64px;
}

.article-list-item {
  text-decoration: none;
  color: #6a6a6a;
  padding: 16px 0 0 0;
}

.article-list-item p {
  padding-bottom: 32px;
  border-bottom: 1px solid #42554b;
}

#add-comment-form {
  border-bottom: 2px solid black;
  padding: 16px;
}

#add-comment-form h3 {
  margin-bottom: 16px;
}

#add-comment-form input, textarea {
  display: block;
  width: 300px;
  padding: 8px;
  border-radius: 8px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  font-size: 18px;
  border: 2px solid black;
  margin-bottom: 16px;
}

#add-comment-form button {
  width: 320px;
}

#upvotes-section button, p {
  display: inline-block;
  margin-right: 16px;
}

.comment {
  border-bottom: 2px solid black;
  padding: 16px;
}

Components

AddCommentForm.js

import React, {useState} from "react";

const AddCommentForm = ({articleName, setArticleInfo})=>{

  const [username, setUsername] = useState("");
  const [commentText, setCommentText] = useState("");

  const addComment = async()=>{
    const result = await fetch(`/api/articles/${articleName}/add-comment`, {
      method:"POST",
      body:JSON.stringify({username, text:commentText}),
      headers:{
        "Content-Type":"application/json",
      }
    })
    const body = await result.json();
    setArticleInfo(body);
    setUsername("");
    setCommentText("");
  }

  return(
    <div id="add-comment-form">
      <h3>Add a Comment</h3>
      <label>
        Name:
        <input type="text" value={username} onChange={(event)=>setUsername(event.target.value)}/>
      </label>
      <label>
        Comment:
        <textarea row="4" cols="50" value={commentText} onChange={(event)=>setCommentText(event.target.value)}/>
      </label>
      <button onClick={()=> addComment()}>Add Comment</button>
    </div>
    )
};

export default AddCommentForm;

ArticleList.js

import React from 'react';
import {Link} from "react-router-dom";

const ArticleList = ({articles})=> (
    <>
      {articles.map((article, key)=>(
        <Link className="article-list-item" key={key} to={`/article/${article.name}`}>
          <h3>{article.title}</h3>
          <p>{article.content[0].substring(0, 150)} ...</p>
        </Link>
      ))}
    </>
);

export default ArticleList;

CommentsList.js

import React from "react";

const CommentsList = ({comments}) => (
  <>
    {comments.map((comment, key) => (
      <div className="comment" key={key}>
        <h4>{comment.username}</h4>
        <p>{comment.text}</p>
      </div>
    ))}
  </>
);

export default CommentsList;

UpvotesSection.js

import React from "react";

const UpvotesSection=({articleName, upvotes, setArticleInfo})=> {

  const upvoteArticle = async()=>{
    const result = await fetch(`/api/articles/${articleName}/upvote`,{
      method:'POST',
    })
    const body = await result.json();
    setArticleInfo(body);
  }

  return (
    <div id="upvotes-section">
      <button onClick={()=>upvoteArticle()}>Add Upvote</button>
      <p>This post has been upvoted {upvotes} times</p>
    </div>
  );

}

export default UpvotesSection;

Pages

AboutPage.js

import React from 'react';

const AboutPage = () => (
  <>
    <h1>About me</h1>

    <p>Lorem ipsum dolor sit amet, has aperiam virtute ea, sale labores utroque his in. An sit ullum erroribus, omnes
      option at eam. Fuisset tibique accusamus vim id, ne sea tation feugiat vulputate. Sea cu vocent adversarium
      efficiantur, eu nec ridens atomorum definiebas, at duo omnium salutandi.</p>

    <p>Id his assum melius aliquid, mel malis iisque at. Id usu eros laudem elaboraret, an duo omnis everti vulputate,
      porro adolescens concludaturque ut usu. Alii dicunt euripidis in usu, has porro recusabo eu, vel albucius tincidunt ut.
      In qui velit choro neglegentur, eam at oratio commodo copiosae, vitae laudem in ius. Quaestio torquatos reprehendunt
      pri ei, eam et quando constituto, ei duo mandamus adipiscing. Dicam molestiae honestatis his no, veri ubique lobortis
      ad pro.</p>
  </>

);

export default AboutPage;

article-content.js

const articles = [
  {
    name: 'learn-react',
    title: 'The Fastest Way to Learn React',
    content: [
      `Welcome! Today we're going to be talking about the fastest way to learn React. We'll be discussing some topics such as proin congue ligula id risus posuere, vel eleifend ex egestas. Sed in turpis leo. Aliquam malesuada in massa tincidunt egestas. Nam consectetur varius turpis, non porta arcu porttitor non. In tincidunt vulputate nulla quis egestas. Ut eleifend ut ipsum non fringilla. Praesent imperdiet nulla nec est luctus, at sodales purus euismod.`, `Donec vel mauris lectus. Etiam nec lectus urna. Sed sodales ultrices dapibus. Nam blandit tristique risus, eget accumsan nisl interdum eu. Aenean ac accumsan nisi. Nunc vel pulvinar diam. Nam eleifend egestas viverra. Donec finibus lectus sed lorem ultricies, eget ornare leo luctus. Morbi vehicula, nulla eu tempor interdum, nibh elit congue tellus, ac vulputate urna lorem nec nisi. Morbi id consequat quam. Vivamus accumsan dui in facilisis aliquet.`, `Etiam nec lectus urna. Sed sodales ultrices dapibus. Nam blandit tristique risus, eget accumsan nisl interdum eu. Aenean ac accumsan nisi. Nunc vel pulvinar diam. Nam eleifend egestas viverra. Donec finibus lectus sed lorem ultricies, eget ornare leo luctus. Morbi vehicula, nulla eu tempor interdum, nibh elit congue tellus, ac vulputate urna lorem nec nisi. Morbi id consequat quam. Vivamus accumsan dui in facilisis aliquet.`,
    ]
  }, {
    name: 'learn-node',
    title: 'How to Build a Node Server in 10 Minutes',
    content: [
      `In this article, we're going to be talking looking at a very quick way to set up a Node.js server. We'll be discussing some topics such as proin congue ligula id risus posuere, vel eleifend ex egestas. Sed in turpis leo. Aliquam malesuada in massa tincidunt egestas. Nam consectetur varius turpis, non porta arcu porttitor non. In tincidunt vulputate nulla quis egestas. Ut eleifend ut ipsum non fringilla. Praesent imperdiet nulla nec est luctus, at sodales purus euismod.`,
      `Donec vel mauris lectus. Etiam nec lectus urna. Sed sodales ultrices dapibus. Nam blandit tristique risus, eget accumsan nisl interdum eu. Aenean ac accumsan nisi. Nunc vel pulvinar diam. Nam eleifend egestas viverra. Donec finibus lectus sed lorem ultricies, eget ornare leo luctus. Morbi vehicula, nulla eu tempor interdum, nibh elit congue tellus, ac vulputate urna lorem nec nisi. Morbi id consequat quam. Vivamus accumsan dui in facilisis aliquet.`,
      `Etiam nec lectus urna. Sed sodales ultrices dapibus. Nam blandit tristique risus, eget accumsan nisl interdum eu. Aenean ac accumsan nisi. Nunc vel pulvinar diam. Nam eleifend egestas viverra. Donec finibus lectus sed lorem ultricies, eget ornare leo luctus. Morbi vehicula, nulla eu tempor interdum, nibh elit congue tellus, ac vulputate urna lorem nec nisi. Morbi id consequat quam. Vivamus accumsan dui in facilisis aliquet.`,
    ]
  }, {
    name: 'my-thoughts-on-resumes',
    title: 'My Thoughts on Resumes',
    content: [
      `Today is the day I talk about something which scares most people: resumes. In reality, I'm not sure why people have such a hard time with proin congue ligula id risus posuere, vel eleifend ex egestas. Sed in turpis leo. 
       Aliquam malesuada in massa tincidunt egestas. Nam consectetur varius turpis, non porta arcu porttitor non. In tincidunt vulputate nulla quis egestas. Ut eleifend ut ipsum non fringilla. Praesent imperdiet nulla nec est luctus, at sodales purus euismod.`, `Donec vel mauris lectus. Etiam nec lectus urna. Sed sodales ultrices dapibus. 
       Nam blandit tristique risus, eget accumsan nisl interdum eu. Aenean ac accumsan nisi. Nunc vel pulvinar diam. Nam eleifend egestas viverra. Donec finibus lectus sed lorem ultricies, eget ornare leo luctus. Morbi vehicula, nulla eu tempor interdum, nibh elit congue tellus, ac vulputate urna lorem nec nisi. Morbi id consequat quam. Vivamus accumsan dui in facilisis aliquet.`, `Etiam nec lectus urna. Sed sodales ultrices dapibus.
      Nam blandit tristique risus, eget accumsan nisl interdum eu. Aenean ac accumsan nisi. Nunc vel pulvinar diam. Nam eleifend egestas viverra. Donec finibus lectus sed lorem ultricies, eget ornare leo luctus. Morbi vehicula, nulla eu tempor interdum, nibh elit congue tellus, ac vulputate urna lorem nec nisi. Morbi id consequat quam. Vivamus accumsan dui in facilisis aliquet.`,
    ]
  },
];

export default articles;

ArticleListPage.js

import React from 'react';
import ArticleList from '../components/ArticleList'
import articleContent from './article-content';


const ArticleListPage = () => (
  <>
    <h1>Articles</h1>

    <ArticleList articles={articleContent}/>

  </>

);

export default ArticleListPage;

ArticlePage.js

import React, {useEffect, useState} from 'react';
import ArticleList from "../components/ArticleList";
import CommentsList from "../components/CommentsList";
import UpvotesSection from "../components/UpvotesSection";
import AddCommentForm from "../components/AddCommentForm";
import NotFoundPage from "./NotFoundPage";
import articleContent from './article-content'

const ArticlePage = ({match}) => {

  // fetch('/api/articles')


  const name = match.params.name;
  const article = articleContent.find(article => article.name === name);

  const [articleInfo, setArticleInfo] = useState({upvotes:0, comments:[]});

  useEffect(()=>{
    const fetchData = async()=>{
      const result = await fetch(`/api/articles/${name}`)
      const body = await result.json();
      setArticleInfo(body)
    }
    fetchData();
    // setArticleInfo({upvotes: Math.ceil(Math.random()*10)});
  }, [name]);

  if(!article) return <NotFoundPage/>

  const otherArticles = articleContent.filter(article => article.name !== name);

  return (
    <>
      <h1>{article.title}</h1>
      <UpvotesSection articleName={name} upvotes={articleInfo.upvotes} setArticleInfo={setArticleInfo} />
      {article.content.map((paragraph, key)=>(
        <p key={key}>{paragraph}</p>
      ))}

      <CommentsList comments={articleInfo.comments}/>
      <AddCommentForm articleName={name} setArticleInfo={setArticleInfo} />


      <h3>Other Articles</h3>
      <ArticleList articles={otherArticles}/>

    </>
  );

};

export default ArticlePage;

HomePage.js

import React from 'react';
import logo from '../react.svg';
import mongodbsvg from '../mongodb.svg';
import expresssvg from '../express.svg';
import rpisvg from '../raspberry-pi.svg';
import nginxsvg from '../nginx-1.svg';

const HomePage = () => (
  <>
    <h1>Hello, welcome to my blog!</h1>

    <small>This is a Reactjs based blog example site, backended by MongoDB and ExpressJS, served by nginx on Raspberry Pi 4 setup in my laundry room, I know you want to ask why I'm not using AWS, I don't have the budget.</small>
    <br/>
    <p><img className="logoimg" src={logo} alt="Logo" /></p>
    <p><img className="logoimg" src={mongodbsvg} alt="Logo" /></p>
    <p><img className="logoimg" src={expresssvg} alt="Logo" /></p>
    <p><img className="logoimg" src={rpisvg} alt="Logo" /></p>
    <p><img className="logoimg" src={nginxsvg} alt="Logo" /></p>
  </>

);

export default HomePage;

NotFoundPage.js

import React from "react";

const NotFoundPage = () => (
  <h1>404: Page Not Found</h1>
);

export default NotFoundPage;