Categories
React Projects

Create an Image Slider App with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to image slider app with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app image-slider

with NPM to create our React project.

Create the Image Slider App

To create the image slider app, we write:

import { useState } from "react";

const images = [
  "https://i.picsum.photos/id/1/200/300.jpg?hmac=jH5bDkLr6Tgy3oAg5khKCHeunZMHq0ehBZr6vGifPLY",
  "https://i.picsum.photos/id/2/200/300.jpg?hmac=HiDjvfge5yCzj935PIMj1qOf4KtvrfqWX3j4z1huDaU",
  "https://i.picsum.photos/id/3/200/300.jpg?hmac=o1-38H2y96Nm7qbRf8Aua54lF97OFQSHR41ATNErqFc"
];

export default function App() {
  const [index, setIndex] = useState(0);

  const next = () => {
    setIndex((i) => (i + 1) % images.length);
  };

  const prev = () => {
    setIndex((i) => (i - 1) % images.length);
  };

  return (
    <div className="App">
      <button onClick={prev}>&lt;</button>
      <img src={images[index]} alt="" />
      <button onClick={next}>&gt;</button>
    </div>
  );
}

We have 3 image URLs in the images array.

In the App component, we have the index state, which is set by setIndex .

We set its initial value to 0.

Then we have the next and prev functions to set the index by incrementing by 1 and then get the remainder of that by dividing by images.length .

This gets the index of the next image to show.

prev is similar except we subtract by 1 instead of add to get the index of the previous image.

We go back to the first one after we reach the last image and we’re clicking the next button.

And we show the last image after we reach the first image and click on the previous button.

Then we return JSX with buttons to call the next and prev functions when we click them.

We show the image in the img element.

We can also make an image slider that moves to the next slide automatically by writing:

import { useEffect, useState } from "react";

const images = [
  "https://i.picsum.photos/id/1/200/300.jpg?hmac=jH5bDkLr6Tgy3oAg5khKCHeunZMHq0ehBZr6vGifPLY",
  "https://i.picsum.photos/id/2/200/300.jpg?hmac=HiDjvfge5yCzj935PIMj1qOf4KtvrfqWX3j4z1huDaU",
  "https://i.picsum.photos/id/3/200/300.jpg?hmac=o1-38H2y96Nm7qbRf8Aua54lF97OFQSHR41ATNErqFc"
];

export default function App() {
  const [index, setIndex] = useState(0);

  const next = () => {
    setIndex((i) => (i + 1) % images.length);
  };

  useEffect(() => {
    const timer = setInterval(() => {
      next();
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return (
    <div className="App">
      <img src={images[index]} alt="" />
    </div>
  );
}

Most of the code is the same except that we removed the prev function.

And we added the useEffect hook with a callback that calls next every second with setInterval .

We return a callback to call clearIntveral to clear the timer when we unmount the component.

The empty array in the 2nd argument lets us run the useEffect callback only when we mount the component.

Conclusion

We can create an image slider easily with React and JavaScript.

Categories
React Projects

Create a Counter App with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create a counter app with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app counter

with NPM to create our React project.

Create the Counter App

To create the counter app, we write:

import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount((c) => c + 1);
  };

  const decrement = () => {
    setCount((c) => c - 1);
  };

  return (
    <div className="App">
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
      <p>{count}</p>
    </div>
  );
}

We have the count state created by the useState hook.

We set the initial value to 0.

Then we have the increment and decrement functions to increase and decrease count by 1 respectively.

We pass in a function that has the current value of the count state as the parameter and return the new value in the setCount callbacks.

Then we have buttons that calls increment and decrement respectively when we click them.

And we display the count as the result of our clicks.

Conclusion

We can create a counter app with React and JavaScript.

Categories
React Projects

Create an Image Slider App with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to image slider app with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app image-slider

with NPM to create our React project.

Create the Image Slider App

To create the image slider app, we write:

import { useState } from "react";

const images = [
  "https://i.picsum.photos/id/1/200/300.jpg?hmac=jH5bDkLr6Tgy3oAg5khKCHeunZMHq0ehBZr6vGifPLY",
  "https://i.picsum.photos/id/2/200/300.jpg?hmac=HiDjvfge5yCzj935PIMj1qOf4KtvrfqWX3j4z1huDaU",
  "https://i.picsum.photos/id/3/200/300.jpg?hmac=o1-38H2y96Nm7qbRf8Aua54lF97OFQSHR41ATNErqFc"
];

export default function App() {
  const [index, setIndex] = useState(0);

  const next = () => {
    setIndex((i) => (i + 1) % images.length);
  };

  const prev = () => {
    setIndex((i) => (i - 1) % images.length);
  };

  return (
    <div className="App">
      <button onClick={prev}>&lt;</button>
      <img src={images[index]} alt="" />
      <button onClick={next}>&gt;</button>
    </div>
  );
}

We have 3 image URLs in the images array.

In the App component, we have the index state, which is set by setIndex .

We set its initial value to 0.

Then we have the next and prev functions to set the index by incrementing by 1 and then get the remainder of that by dividing by images.length .

This gets the index of the next image to show.

prev is similar except we subtract by 1 instead of add to get the index of the previous image.

We go back to the first one after we reach the last image and we’re clicking the next button.

And we show the last image after we reach the first image and click on the previous button.

Then we return JSX with buttons to call the next and prev functions when we click them.

We show the image in the img element.

We can also make an image slider that moves to the next slide automatically by writing:

import { useEffect, useState } from "react";

const images = [
  "https://i.picsum.photos/id/1/200/300.jpg?hmac=jH5bDkLr6Tgy3oAg5khKCHeunZMHq0ehBZr6vGifPLY",
  "https://i.picsum.photos/id/2/200/300.jpg?hmac=HiDjvfge5yCzj935PIMj1qOf4KtvrfqWX3j4z1huDaU",
  "https://i.picsum.photos/id/3/200/300.jpg?hmac=o1-38H2y96Nm7qbRf8Aua54lF97OFQSHR41ATNErqFc"
];

export default function App() {
  const [index, setIndex] = useState(0);

  const next = () => {
    setIndex((i) => (i + 1) % images.length);
  };

  useEffect(() => {
    const timer = setInterval(() => {
      next();
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return (
    <div className="App">
      <img src={images[index]} alt="" />
    </div>
  );
}

Most of the code is the same except that we removed the prev function.

And we added the useEffect hook with a callback that calls next every second with setInterval .

We return a callback to call clearIntveral to clear the timer when we unmount the component.

The empty array in the 2nd argument lets us run the useEffect callback only when we mount the component.

Conclusion

We can create an image slider easily with React and JavaScript.

Categories
React

How to Add a Rich Text Editor into Your React App

You can easily add basic rich text editor capabilities to your web app. The capabilities of WordPad, at least, can be added with some simple changes to your app. CKEditor is one of the most popular rich text editors that are incorporated into web apps. There are versions for all the most popular web frameworks, including React.

In this piece, we’ll make an app that lets users enter their document in a rich text editor and generate a PDF from it. We will use Express for back-end and React for front-end.

PDF stands for Portable Document Format. It’s a popular format for exchanging documents. Therefore, a lot of apps feature the ability to create PDFs from other kinds of documents. Creating PDFs is easy in Node.js apps with third party libraries. We can easily make an app from it ourselves.

Back-End

We will start with the back-end. To start, we create a project folder with the backend folder inside. Then in the backend folder run npx express-generator to create the Express app. Then run npm i to install the packages listed in package.json .

Next, we install our own packages. We need Babel to run the app with the latest version of JavaScript, CORS for cross domain requests with front end, HTML-PDF for converting HTML strings to PDF, Multer for file upload, Sequelize for ORM, and SQLite3 for our database.

We install all of these by running npm i @babel/cli @babel/core @babel/node @babel/preset-env cors html-pdf sequelize sqlite3 multer .

After that we change the scripts section of package.json to have:

"start": "nodemon --exec npm run babel-node --  ./bin/www",
"babel-node": "babel-node"

This is so that we run our app with Babel instead of the regular Node runtime.

Then we create a .babelrc file in the backend folder and add this:

{
    "presets": [
        "@babel/preset-env"
    ]
}

This specifies that we run our app with the latest version of JavaScript.

Next we add our database code. Run npx sequelize-cli init in the backend folder to create the Sequelize code.

We should have a config.js in the project now. In there, add this:

{
  "development": {
    "dialect": "sqlite",
    "storage": "development.db"
  },
  "test": {
    "dialect": "sqlite",
    "storage": "test.db"
  },
  "production": {
    "dialect": "sqlite",
    "storage": "production.db"
  }
}

This specifies that we use SQLite for our database.

Next we create our model and migration by running this:

npx sequelize-cli model:create --name Document --attributes name:string,document:text,pdfPath:string

This creates a Document model and Documents table.

We then run this to create the database:

npx sequelize-cli db:migrate

Next we create our routes. Create a pdf.js file in the routes folder and add this:

var express = require("express");
var pdf = require("html-pdf");
const models = require("../models");
var multer = require("multer");
const fs = require("fs");
var router = express.Router();
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "./files");
  },
  filename: (req, file, cb) => {
    cb(null, `${file.fieldname}_${+new Date()}.jpg`);
  }
});

const upload = multer({
  storage
});

router.get("/", async (req, res, next) => {
  const documents = await models.Document.findAll();
  res.json(documents);
});

router.post("/", async (req, res, next) => {
  const document = await models.Document.create(req.body);
  res.json(document);
});

router.put("/:id", async (req, res, next) => {
  const id = req.params.id;
  const { name, document } = req.body;
  const doc = await models.Document.update(
    { name, document },
    { where: { id } }
  );
  res.json(doc);
});

router.delete("/:id", async (req, res, next) => {
  const id = req.params.id;
  await models.Document.destroy({ where: { id } });
  res.json({});
});

router.get("/generatePdf/:id", async (req, res, next) => {
  const id = req.params.id;
  const documents = await models.Document.findAll({ where: { id } });
  const document = documents[0];
  const stream = await new Promise((resolve, reject) => {
    pdf.create(document.document).toStream((err, stream) => {
      if (err) {
        reject(reject);
        return;
      }
      resolve(stream);
    });
  });

const fileName = `${+new Date()}.pdf`;
  const pdfPath = `${__dirname}/../files/${fileName}`;
  stream.pipe(fs.createWriteStream(pdfPath));
  const doc = await models.Document.update(
    { pdfPath: fileName },
    { where: { id } }
  );
  res.json(doc);
});

router.post("/uploadImage", upload.single('upload'), async (req, res, next) => {
  res.json({
    uploaded: true,
    url: `${process.env.BASE_URL}/${req.file.filename}`
  });
});

module.exports = router;

We do the standard CRUD operations to the Documents table in the first four routes. We have GET for getting all the Documents , POST for create a Document from post parameters, PUT for updating Document by ID, DELETE for deleting a Document by looking it up by ID. We have the HTML in the document field for generating the PDF later.

The generatePdf is for generating the PDF. We get the ID from the URL and then use the HTML-PDF package to generate the PDF. We generate the PDF by converting the HTML document to a file stream object with the HTML-PDF package and then writing the stream to a file and, finally, saving the path to the file in the Document entry in with the ID in the URL parameter.

We also have an uploadImage route to let user upload images with CKEditor with the CKFinder plugin. The plugin expects uploaded and url in the response, so we return those.

Then we need to add a files folder to the backend folder.

Next in app.js , we replace the existing code with this:

var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");
var cors = require("cors");

var indexRouter = require("./routes/index");
var pdfRouter = require("./routes/pdf");

var app = express();

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));
app.use(express.static(path.join(__dirname, "files")));
app.use(cors());

app.use("/", indexRouter);
app.use("/pdf", pdfRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get("env") === "development" ? err : {};

// render the error page
  res.status(err.status || 500);
  res.render("error");
});

module.exports = app;

We expose the file folder with this:

app.use(express.static(path.join(__dirname, "files")));

We expose the pdf route with this:

var pdfRouter = require("./routes/pdf");
app.use("/pdf", pdfRouter);

Front-End

Now the back-end is done, we can move onto the front-end. Create the React app by running Create React App. We run npx create-react-app frontend in the root folder of the project.

We then install our packages. We will use CKEditor for our rich text editor, Axios for making HTTP requests, Bootstrap for styling, MobX for simple state management, React Router for routing URLs to components, and Formik and Yup for form value handling and form validation respectively.

Install all the packages by running npm i @ckeditor/ckeditor5-build-classic @ckeditor/ckeditor5-react axios bootstrap formik mobx mobx-react react-bootstrap react-router-dom yup .

With the packages installed, we can get started. In App.js , we replace the existing code with this:

import React from "react";
import HomePage from "./HomePage";
import { Router, Route } from "react-router-dom";
import { createBrowserHistory as createHistory } from "history";
import TopBar from "./TopBar";
import { DocumentStore } from "./store";
import "./App.css";
const history = createHistory();
const documentStore = new DocumentStore();

function App() {
  return (
    <div className="App">
      <Router history={history}>
        <TopBar />
        <Route
          path="/"
          exact
          component={props => (
            <HomePage {...props} documentStore={documentStore} />
          )}
        />
      </Router>
    </div>
  );
}

export default App;

This adds our top bar and route to the home page.

In App.css, we replace the existing code with this:

.page {
  padding: 20px;
}

.content-invalid-feedback {
  width: 100%;
  margin-top: 0.25rem;
  font-size: 80%;
  color: #dc3545;
}

nav.navbar {
  background-color: green !important;
}

This adds some padding to our page. It also styles the validation message for the Rich text editor and changes the color of the navbar.

Next we create the form for adding and editing documents. Create a DocumentForm.js in the src file and add this:

import React from "react";
import * as yup from "yup";
import Form from "react-bootstrap/Form";
import Col from "react-bootstrap/Col";
import Button from "react-bootstrap/Button";
import { observer } from "mobx-react";
import { Formik, Field } from "formik";
import { addDocument, editDocument, getDocuments, APIURL } from "./request";
import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

const schema = yup.object({
  name: yup.string().required("Name is required")
});

function DocumentForm({ documentStore, edit, onSave, doc }) {
  const [content, setContent] = React.useState("");
  const [dirty, setDirty] = React.useState(false);

  const handleSubmit = async evt => {
    const isValid = await schema.validate(evt);
    if (!isValid || !content) {
      return;
    }
    const data = { ...evt, document: content };
    if (!edit) {
      await addDocument(data);
    } else {
      await editDocument(data);
    }
    getAllDocuments();
  };

  const getAllDocuments = async () => {
    const response = await getDocuments();
    documentStore.setDocuments(response.data);
    onSave();
  };

  return (
    <>
      <Formik
        validationSchema={schema}
        onSubmit={handleSubmit}
        initialValues={edit ? doc : {}}
      >
        {({
          handleSubmit,
          handleChange,
          handleBlur,
          values,
          touched,
          isInvalid,
          errors
        }) => (
          <Form noValidate onSubmit={handleSubmit}>
            <Form.Row>
              <Form.Group as={Col} md="12" controlId="name">
                <Form.Label>Name</Form.Label>
                <Form.Control
                  type="text"
                  name="name"
                  placeholder="Name"
                  value={values.name || ""}
                  onChange={handleChange}
                  isInvalid={touched.name && errors.name}
                />
                <Form.Control.Feedback type="invalid">
                  {errors.name}
                </Form.Control.Feedback>
              </Form.Group>
            </Form.Row>

            <Form.Row>
              <Form.Group as={Col} md="12" controlId="content">
                <Form.Label>Content</Form.Label>
                <CKEditor
                  editor={ClassicEditor}
                  data={content || ""}
                  onInit={editor => {
                    if (edit) {
                      setContent(doc.document);
                    }
                  }}
                  onChange={(event, editor) => {
                    const data = editor.getData();
                    setContent(data);
                    setDirty(true);
                  }}
                  config={{
                    ckfinder: {
                      uploadUrl:
                        `${APIURL}/pdf/uploadImage`
                    }
                  }}
                />
                <div className="content-invalid-feedback">
                  {dirty && !content ? "Content is required" : null}
                </div>
              </Form.Group>
            </Form.Row>

            <Button type="submit" style={{ marginRight: 10 }}>
              Save
            </Button>
            <Button type="button">Cancel</Button>
          </Form>
        )}
      </Formik>
    </>
  );
}

export default observer(DocumentForm);

We wrap our React Bootstrap Form inside the Formik component to get the form handling function from Formik, which we use directly in the React Bootstrap form fields. We can’t do the same with CKEditor, so we write our own form handlers for the rich text editor. We set the data prop in the CKEditor to set the value of the input of the rich text editor. The onInit function is used with users try to edit an existing document since we have to set the data prop with the editor initialized by running setContent(doc.document); . The onChange prop is the handler function for setting content whenever it is updated — so the data prop will have the latest value, which we will submit when the user clicks Save.

We use the CKFinder plugin to upload images. To make it work, we set the image upload URL to the URL of the upload route in our back-end.

By default, we have simple text styling buttons like bold and italics, we can also add pictures and videos into our document. Also we can add tables and lists without making any changes to our editor.

The form validation schema is provided by the Yup schema object which we create at the top of the code. We need to check if the name field is filled in.

The handleSubmit function is for handling submitting the data to back end. We check both the content and the evt object to check both fields since we cannot incorporate the Formik form handlers directly into the CKEditor component.

If everything is valid, then we add a new document or update it depending on whether the edit prop is true or not.

Then, when saving is successful, we call getAllDocuments to populate the latest documents into our MobX store by running documentStore.setDocuments(response.data);.

Next, we make our home-page by creating HomePage.js in the src folder and add this:

import React, { useState, useEffect } from "react";
import { withRouter } from "react-router-dom";
import DocumentForm from "./DocumentForm";
import Modal from "react-bootstrap/Modal";
import ButtonToolbar from "react-bootstrap/ButtonToolbar";
import Button from "react-bootstrap/Button";
import Table from "react-bootstrap/Table";
import { observer } from "mobx-react";
import { getDocuments, deleteDocument, generatePDF, APIURL } from "./request";

function HomePage({ documentStore, history }) {
  const [openAddModal, setOpenAddModal] = useState(false);
  const [openEditModal, setOpenEditModal] = useState(false);
  const [initialized, setInitialized] = useState(false);
  const [doc, setDoc] = useState([]);

  const openAddTemplateModal = () => {
    setOpenAddModal(true);
  };

  const closeAddModal = () => {
    setOpenAddModal(false);
    setOpenEditModal(false);
  };

  const cancelAddModal = () => {
    setOpenAddModal(false);
  };

  const cancelEditModal = () => {
    setOpenEditModal(false);
  };

  const getAllDocuments = async () => {
    const response = await getDocuments();
    documentStore.setDocuments(response.data);
    setInitialized(true);
  };

  const editTemplate = d => {
    setDoc(d);
    setOpenEditModal(true);
  };

  const onSave = () => {
    cancelAddModal();
    cancelEditModal();
  };

  const deleteSingleDocument = async id => {
    await deleteDocument(id);
    getAllDocuments();
  };

  const generateSinglePdf = async id => {
    await generatePDF(id);
    alert("PDF Generated");
    getAllDocuments();
  };

  useEffect(() => {
    if (!initialized) {
      getAllDocuments();
    }
  });

  return (
    <div className="page">
      <h1 className="text-center">Documents</h1>
      <ButtonToolbar onClick={openAddTemplateModal}>
        <Button variant="primary">Add Document</Button>
      </ButtonToolbar>

      <Modal show={openAddModal} onHide={closeAddModal}>
        <Modal.Header closeButton>
          <Modal.Title>Add Document</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <DocumentForm
            onSave={onSave.bind(this)}
            cancelModal={cancelAddModal.bind(this)}
            documentStore={documentStore}
          />
        </Modal.Body>
      </Modal>

      <Modal show={openEditModal} onHide={cancelEditModal}>
        <Modal.Header closeButton>
          <Modal.Title>Edit Document</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <DocumentForm
            edit={true}
            doc={doc}
            onSave={onSave.bind(this)}
            cancelModal={cancelEditModal.bind(this)}
            documentStore={documentStore}
          />
        </Modal.Body>
      </Modal>
      <br />
      <Table striped bordered hover>
        <thead>
          <tr>
            <th>Name</th>
            <th>PDF</th>
            <th>Generate PDF</th>
            <th>Edit</th>
            <th>Delete</th>
          </tr>
        </thead>
        <tbody>
          {documentStore.documents.map(d => {
            return (
              <tr key={d.id}>
                <td>{d.name}</td>
                <td>
                  <a href={`${APIURL}/${d.pdfPath}`} target="_blank">
                    Open
                  </a>
                </td>
                <td>
                  <Button
                    variant="outline-primary"
                    onClick={generateSinglePdf.bind(this, d.id)}
                  >
                    Generate PDF
                  </Button>
                </td>
                <td>
                  <Button
                    variant="outline-primary"
                    onClick={editTemplate.bind(this, d)}
                  >
                    Edit
                  </Button>
                </td>
                <td>
                  <Button
                    variant="outline-primary"
                    onClick={deleteSingleDocument.bind(this, d.id)}
                  >
                    Delete
                  </Button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </Table>
    </div>
  );
}

export default withRouter(observer(HomePage));

We have a React Bootstrap table for listing the documents with buttons to edit, deleting documents, and generating PDFs. Also, there’s an “open” link for opening the PDF in each row. We have a “create” button on top of the table.

When the page loads, we call getAllDocuments and populate them in the MobX store. We open and close the add and edit modals with the openAddTemplateModal, closeAddModal, cancelAddModal, cancelEditModal functions.

Next, create request.js in the src folder and add this:

export const APIURL = "http://localhost:3000";
const axios = require("axios");

export const getDocuments = () => axios.get(`${APIURL}/pdf`);

export const addDocument = data => axios.post(`${APIURL}/pdf`, data);

export const editDocument = data => axios.put(`${APIURL}/pdf/${data.id}`, data);

export const deleteDocument = id => axios.delete(`${APIURL}/pdf/${id}`);

export const generatePDF = id => axios.get(`${APIURL}/pdf/generatePdf/${id}`);

This adds the functions to make requests to our routes in the back end.

Then we create our MobX store. Create store.js in the src folder and put this in it:

import { observable, action, decorate } from "mobx";

class DocumentStore {
  documents = [];

setDocuments(documents) {
    this.documents = documents;
  }
}

DocumentStore = decorate(DocumentStore, {
  documents: observable,
  setDocuments: action
});

export { DocumentStore };

We have the function setDocuments to put the photo data in the store. We used it in HomePage and DocumentForm and we instantiated it before exporting so that we only have to do it in one place.

This block:

DocumentStore = decorate(DocumentStore, {
  documents: observable,
  setDocuments: action
});

designates the documents array in DocumentStore as the entity that can be watched by components for changes. The setDocuments function is designated as the function that can be used to set the documents array in the store.

Next, we create the top bar by creating a TopBar.js file in the src folder and add:

import React from "react";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import { withRouter } from "react-router-dom";

function TopBar({ location }) {
  return (
    <Navbar bg="primary" expand="lg" variant="dark">
      <Navbar.Brand href="#home">PDF App</Navbar.Brand>
      <Navbar.Toggle aria-controls="basic-navbar-nav" />
      <Navbar.Collapse id="basic-navbar-nav">
        <Nav className="mr-auto">
          <Nav.Link href="/" active={location.pathname == "/"}>
            Home
          </Nav.Link>
        </Nav>
      </Navbar.Collapse>
    </Navbar>
  );
}

export default withRouter(TopBar);

This contains the React Bootstrap Navbar to show a top bar with a link to the home page and the name of the app. We only display it with the token present in local storage. We check the pathname to highlight the right links by setting the active prop.

Next in index.html , we replace the existing code with this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>PDF App</title>
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

This adds the Bootstrap CSS and changes the title.

After writing all that code, we can run our app. First, install nodemon by running npm i -g nodemon so that we don’t have to restart the back-end ourselves when the files change.

Then run the back-end by running npm start in the backend folder and npm start in the frontend folder. Choose ‘yes’ if you’re asked to run it from a different port.

Categories
React

Using Bit.dev with Gatsby

Bit.dev is a great snippet repository to let us add store our front end components as standalone packages.

In this article, we’ll take look at how to add create component packages that are created for Gatsby and then use them in another Gatsby project.

Build a Gatsby Landing Page

The first step is to build a simple landing page component in our first Gatsby project. Then we’ll extract that component from it with the Bit CLI later and import it to the other Gatsby project and use it.

Make sure that we use recent version of Node like 10.x or later before we run anything.

First, we’ve to install the Gatsby CLI by running:

npm install -g gatsby-cli

Then we create a new Gatsby project by running:

gatsby new gatsby-landing-page

Once we did that, we start our development server by running:

gatsby develop

Next, we create a landing-page folder in the components folder and then create and index.js and banner.jpg file inside it.

We have to do so we can export the whole folder as a package with Bit CLI later.

The root JavaScript file must be called index.js. Then we put in the following code into index.js:

import React from "react"
import BannerImage from './banner.jpg'

const LandingPage = () => {
  return (
    <div className='center'>
      <h1 >Buy Now</h1>
      <p>Use Bit to store your code now.</p>
      <div style={{ maxWidth: `100vw`, marginBottom: `1.45rem` }}>
        <img src={BannerImage} alt='landing page image' />
      </div>
      <button>Subscribe to Bit Now</button>
    </div>
  )
}

export default LandingPage

We have to import the image file directly since we can’t use the GraphQL hooks in a compiled package since the GraphQL queries are run at compile time.

If we use the useStaticQuery hook to include our image, we’ll get an error is we import it later.

Next, we create our own Layout component by creating the layoutcomponent folder in the components folder.

Then we create index.js in there and add the following:

import React from "react"
import Header from "../header"
import "./layout.css"

const Layout = ({ children, title }) => {
  return (
    <>
      <Header siteTitle={title} />
      <div
        style={{
          margin: `0 auto`,
          maxWidth: 960,
          padding: `0 1.0875rem 1.45rem`,
        }}
      >
        <main>{children}</main>
        <footer>
          © {new Date().getFullYear()}, Built with
          {` `}
          <a href="https://www.gatsbyjs.org">Gatsby</a>
        </footer>
      </div>
    </>
  )
}

export default Layout

We move the layout.css file from the generated code into the layoutfolder.

In index.js in the pages folder, we replace what we have with:

import React from "react"
import LandingPage from '../components/landing-page';
import Layout from "../components/layout"
import SEO from "../components/seo"

const IndexPage = () => (
  <div className='center'>
    <Layout title='Buy Now'>
      <SEO title="Home" />
      <LandingPage />
    </Layout>
  </div>
)

export default IndexPage

‘Export’ (Publish) All Reusable UI Components From the Project to Bit.dev

Now that we created our Gatsby landing page in the gatsby-landing-page project. We can export it now to the Bit.

First, we go to bit.dev to create an account.

Then we have to log into it and then create a collection. We can click the ‘+New’ button on the top right, then click Collection to do that.

When we’re asked for the name, enter ‘landing-page’. We’ll leave it public for this example, but it can also be private.

Next, we go to the command line to install the Bit CLI by running:

npm install bit-bin -g

To check that it’s installed, we run:

bit --version

Then we log into the Bit account we just created by running:

bit login

It’ll configure itself automatically. If we want to check the settings that were configured, we can run:

bit config

It’ll add the NPM registry used the Bit to our .npmrc file automatically.

Next, we run bit init to initialize Bit in our gatsby-landing-pageproject.

Then we’ll get a new .bitmap file in our project’s root directory. It tacks Bit components an only includes a comment and line with out Bit version.

In package.json, we’ll see something like the following included:

"bit": {
    "env": {
        "compiler": "bit.envs/compilers/react@1.0.16"
    },
    "componentsDefaultDirectory": "components/{name}",
    "packageManager": "npm"
}

Now we’re ready to build and upload our component files to Bit.

In the command line, we change to our project’s root folder and run:

bit add src/components/landing-page
bit add src/components/header
bit add src/components/layout

Then we’ll see our files added by Bit. We can run bit status to see if it’s been added successfully.

Now we have to install the Bit React compiler by running:

bit import bit.envs/compilers/react --compiler

so that we can build the package and then upload the built package to Bit.

Building the components makes sure that the component is directly consumable by other project and also that the component includes all rthe parts that are required in order to share it with others.

Now we can build the project by running:

bit build

Once we did that, we can tag it with a version number by running:

bit tag --all 0.0.1

We can check the status of tagging by running bit status.

Now we can upload our component to Bit by running:

bit export <username>.landing-page

This will compile and push the component to Bit.

Now when we run bit status, we should see nothing to tag or export since we pushed our built package to Bit.

We can then check what’s uploaded with bit list. To preview our component, we can go to https://bit.dev/<username>/landing-page/landing-page

Create a new Gatsby project.

Now that we uploaded our landing page component to Bit, we can use it in another project.

In this example, we’ll use it in another Gatsby project.

To create a new Gatsby project, we run:

gatsby new gatsby-bit-dev

Import reusable UI components from Bit.dev (the same components you’ve published from the first landing page).

After we created our Gatsby project, we go to the directory for our project and then we have to add the Bit registry to our project by running:

npm config set @bit:registry https://node.bit.dev

since we left our component public. If we make our component private in Bit, we have to run:

npm login --registry=https://node.bit.dev --scope=@bit

Our component package will be available in the @bit/<username>.<collection name>.<component name> format.

There in our example, we’ll run:

npm install @bit/jauyeunggithub.landing-page.landing-page --save

npm install @bit/jauyeunggithub.landing-page.header --save

npm install @bit/jauyeunggithub.landing-page.layout --save

After we run that, we’ll see that we have:

"@bit/jauyeunggithub.landing-page.landing-page": "0.0.1",

in package.json of our gatsby-bit-dev project.

Importing and Using Our Component.

In the components folder, add subscribe-button.js and add:

import React from "react"

const SubscribeButton = () => <button>Subscribe to Newsletter</button>

export default SubscribeButton;

Now to use it, we go to index.js and add:

import React from "react"

import Layout from '@bit/jauyeunggithub.landing-page.layout';
import SEO from "../components/seo"

import LandingPage from '@bit/jauyeunggithub.landing-page.landing-page';
import SubscribeButton from "../components/subscribe-button";

const IndexPage = () => (
  <Layout>
    <SEO title="Home" />
    <LandingPage />
    <div className='center'>
      <SubscribeButton />
    </div>
  </Layout>
)

export default IndexPage

In the code above, we imported our landing page component package that we published to Bit and then referenced it.

We used the SubscribeButton component from our project and the rest of the components are imported from Bit.

Now when we run gatsby develop --port 8001, we’ll see the same landing page as we seen before.

Conclusion

Bit provides us with an easy way to publish Gatsby React component to a central place so that we can use them later,

It just takes a few command to compile a series of components inside one folder into a package and then import it in another project.

To import the package, we just have to add the Bit NPM registry by using npm config and then we can install our package like any other Node package and use it.