<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chandra Panta Chhetri</title>
    <description>The latest articles on DEV Community by Chandra Panta Chhetri (@chandrapantachhetri).</description>
    <link>https://dev.to/chandrapantachhetri</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F367978%2Fcd8f8d25-2382-4957-8f18-417a61814d68.png</url>
      <title>DEV Community: Chandra Panta Chhetri</title>
      <link>https://dev.to/chandrapantachhetri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chandrapantachhetri"/>
    <language>en</language>
    <item>
      <title>Docker, Postgres, Node, Typescript Setup</title>
      <dc:creator>Chandra Panta Chhetri</dc:creator>
      <pubDate>Sun, 09 Jan 2022 13:46:57 +0000</pubDate>
      <link>https://dev.to/chandrapantachhetri/docker-postgres-node-typescript-setup-47db</link>
      <guid>https://dev.to/chandrapantachhetri/docker-postgres-node-typescript-setup-47db</guid>
      <description>&lt;p&gt;When setting up the backend for my project I had many issues related to configuring &amp;amp; connecting to the DB running in a Docker container via Node &amp;amp; PgAdmin. And so, I wanted to explain how I fixed these issues in hopes that it can save you hours of frustrations.&lt;/p&gt;

&lt;p&gt;We will be learning to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure Typescript for Node.js&lt;/li&gt;
&lt;li&gt;Run Node.js &amp;amp; Postgres in Docker containers&lt;/li&gt;
&lt;li&gt;Use env variables in Docker Compose &amp;amp; Node.js&lt;/li&gt;
&lt;li&gt;Connect to the DB running in a container via PgAdmin&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://www.npmjs.com/package/nodemon?activeTab=readme"&gt;Nodemon&lt;/a&gt; to automatically restart the server once the code changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisite
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/products/docker-desktop"&gt;Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Typescript &amp;amp; Nodemon
&lt;/h2&gt;

&lt;p&gt;We will start by creating a basic Express server.&lt;/p&gt;

&lt;p&gt;First, let's install the packages we will need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Dev Dependencies
npm i --save-dev typescript nodemon @types/pg @types/express dotenv

npm i pg express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following scripts in &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
    "start": "node ./dist/app.js",
    "dev": "nodemon -L -e ts --exec \"npm run build &amp;amp;&amp;amp; npm start\"",
    "build": "tsc"
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;build&lt;/code&gt; converts all our &lt;code&gt;.ts&lt;/code&gt; files to &lt;code&gt;.js&lt;/code&gt; and puts it in a &lt;code&gt;dist&lt;/code&gt; folder (as configured below in &lt;code&gt;tsconfig.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dev&lt;/code&gt; uses &lt;code&gt;nodemon&lt;/code&gt; to watch for changes in any &lt;code&gt;.ts&lt;/code&gt; file (&lt;code&gt;'-e ts'&lt;/code&gt;). When there are changes, it will run the &lt;code&gt;build&lt;/code&gt; &amp;amp; &lt;code&gt;start&lt;/code&gt; scripts. Nodemon saves us from having to stop and start the server each time there is a change

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;'-L'&lt;/code&gt; is required when using &lt;code&gt;nodemon&lt;/code&gt; in containers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt; starts up our server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To configure Typescript, create a &lt;code&gt;tsconfig.json&lt;/code&gt; file at the root with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "compilerOptions": {  
      "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
      "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
      "outDir": "./dist" /* Redirect output structure to the directory. */,
      "strict": true /* Enable all strict type-checking options. */,
      "typeRoots": ["./node_modules/@types"] /* List of folders to include type definitions from. */,
      "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
      "skipLibCheck": true /* Skip type checking of declaration files. */,
      "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create an &lt;code&gt;.env&lt;/code&gt; file at the root so that we use the same variables when configuring Docker Compose &amp;amp; the server. Also, we can hide the env variables used in Docker Compose as &lt;code&gt;docker-compose.yml&lt;/code&gt; are commited to Github whereas the &lt;code&gt;.env&lt;/code&gt; file is not.&lt;/p&gt;

&lt;p&gt;For now, add a &lt;code&gt;PORT&lt;/code&gt; variable to set the port the server will run at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT=5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;app.ts&lt;/code&gt; in a new &lt;code&gt;src&lt;/code&gt; folder with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import express, { NextFunction, Request, Response } from "express";
import dotenv from "dotenv";

const app = express();
dotenv.config(); //Reads .env file and makes it accessible via process.env

app.get("/test", (req: Request, res: Response, next: NextFunction) =&amp;gt; {
  res.send("hi");
});

app.listen(process.env.PORT, () =&amp;gt; {
  console.log(`Server is running at ${process.env.PORT}`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify everything is setup correctly thus far, start the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, make a GET request to &lt;code&gt;localhost:5000/test&lt;/code&gt;. The response should be &lt;code&gt;hi&lt;/code&gt;. Also, notice there should be a &lt;code&gt;dist&lt;/code&gt; folder with all the converted &lt;code&gt;.ts&lt;/code&gt; files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;Now, we will run the server &amp;amp; Postgres in a Docker container.&lt;/p&gt;

&lt;p&gt;Before that, you might ask why use Docker at all? &lt;/p&gt;

&lt;p&gt;Docker allows your app to run in isolated environments known as &lt;a href="https://www.docker.com/resources/what-container"&gt;containers&lt;/a&gt;. Consequently, this solves the age-old problem of "the code works on my machine".&lt;/p&gt;

&lt;p&gt;Also, it allows you to use all the tools you want without installing them locally but by using &lt;a href="https://jfrog.com/knowledge-base/a-beginners-guide-to-understanding-and-building-docker-images/#:~:text=A%20Docker%20image%20is%20a,publicly%20with%20other%20Docker%20users."&gt;images&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Docker images can installed from &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt; or created using a &lt;code&gt;Dockerfile&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Create a file named &lt;code&gt;Dockerfile&lt;/code&gt; at the root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Installs Node.js image
FROM node:16.13.1-alpine3.14

# sets the working directory for any RUN, CMD, COPY command
# all files we put in the Docker container running the server will be in /usr/src/app (e.g. /usr/src/app/package.json)
WORKDIR /usr/src/app

# Copies package.json, package-lock.json, tsconfig.json, .env to the root of WORKDIR
COPY ["package.json", "package-lock.json", "tsconfig.json", ".env", "./"]

# Copies everything in the src directory to WORKDIR/src
COPY ./src ./src

# Installs all packages
RUN npm install

# Runs the dev npm script to build &amp;amp; start the server
CMD npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Dockerfile&lt;/code&gt; will build our Express Server as an image, which we can then run in a container.&lt;/p&gt;

&lt;p&gt;When creating applications that use multiple containers, it is best to use &lt;a href="https://docs.docker.com/compose/"&gt;Docker Compose&lt;/a&gt; to configure them.&lt;/p&gt;

&lt;p&gt;But before Docker Compose, let's add some more variables to the &lt;code&gt;.env&lt;/code&gt; file as we will require them shortly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_USER='postgres'
DB_HOST='db'
DB_NAME='db_name'
DB_PASSWORD='password'
DB_PORT=5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DB_HOST&lt;/code&gt; corresponds to the name of the DB service below. This is because each Docker container has its own definition of &lt;code&gt;localhost&lt;/code&gt;. You can think of &lt;code&gt;db&lt;/code&gt; as the container's localhost.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_PORT&lt;/code&gt; is the default port Postgres uses&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_PASSWORD&lt;/code&gt; &amp;amp; &lt;code&gt;DB_USER&lt;/code&gt; are the default auth credentials Postgres uses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file at the root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.8'
services:
  api:
    container_name: api
    restart: always
    build: .
    ports:
      - ${PORT}:${PORT}
    depends_on:
      - db
    volumes:
    - .:/usr/src/app

  db:
    container_name: postgres
    image: postgres
    ports:
      - '5433:${DB_PORT}'
    volumes:
      - data:/data/db
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}

volumes: 
 data: {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: The &lt;code&gt;${VARIABLE_NAME}&lt;/code&gt; syntax lets us use variables from the &lt;code&gt;.env&lt;/code&gt; file. Docker Compose can automatically get variables from the root &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;api&lt;/code&gt; service, we are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;using the &lt;code&gt;Dockerfile&lt;/code&gt; to build the container&lt;/li&gt;
&lt;li&gt;exposing &lt;code&gt;${PORT}&lt;/code&gt; (which was 5000 from the &lt;code&gt;.env&lt;/code&gt; file). When we expose a port, it allows us to access the server via &lt;code&gt;localhost:${PORT}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;only starting the container once the &lt;code&gt;db&lt;/code&gt; service finishes starting up&lt;/li&gt;
&lt;li&gt;mapping all the files in the project directory to &lt;code&gt;WORKDIR&lt;/code&gt; of the container using &lt;a href="https://docs.docker.com/storage/volumes/"&gt;volumes&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the &lt;code&gt;db&lt;/code&gt; service, we are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;using the &lt;code&gt;postgres&lt;/code&gt; image from Docker Hub&lt;/li&gt;
&lt;li&gt;using volumes so that our DB data does not erase when we shut down the container&lt;/li&gt;
&lt;li&gt;mapping port &lt;code&gt;5432&lt;/code&gt; of the container to port &lt;code&gt;5433&lt;/code&gt; of our &lt;code&gt;localhost&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;using env variables from the &lt;code&gt;.env&lt;/code&gt; file and passing it to the &lt;code&gt;postgres&lt;/code&gt; image. The image requires at least the &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt; as per the documentation on &lt;a href="https://hub.docker.com/_/postgres"&gt;Docker Hub&lt;/a&gt;. We also included &lt;code&gt;POSTGRES_DB&lt;/code&gt; as it specifies a different name for the default database that is created when the image is first started&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connecting To Postgres
&lt;/h2&gt;

&lt;p&gt;To connect the server to Postgres container, add the following to &lt;code&gt;app.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Pool } from "pg";
const pool = new Pool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  database: process.env.DB_NAME,
  password: process.env.DB_PASSWORD,
  port: parseInt(process.env.DB_PORT || "5432")
});

const connectToDB = async () =&amp;gt; {
  try {
    await pool.connect();
  } catch (err) {
    console.log(err);
  }
};
connectToDB();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can startup the server &amp;amp; DB by the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will build &amp;amp; start the containers (&lt;code&gt;api&lt;/code&gt; &amp;amp; &lt;code&gt;db&lt;/code&gt;). Remember, first &lt;code&gt;db&lt;/code&gt; will start then &lt;code&gt;api&lt;/code&gt; as &lt;code&gt;api&lt;/code&gt; depends on &lt;code&gt;db&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Try making the same GET request we did earlier and you should get the same response.&lt;/p&gt;

&lt;p&gt;Before we end the tutorial, you might be wondering, how do I view the DB and its contents? There are 2 ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can add a new service to the &lt;code&gt;docker-compose.yml&lt;/code&gt; file that uses the &lt;a href="https://hub.docker.com/r/dpage/pgadmin4/"&gt;pgadmin4&lt;/a&gt; image&lt;/li&gt;
&lt;li&gt;If you have &lt;a href="https://www.pgadmin.org/download/"&gt;PgAdmin&lt;/a&gt; installed locally:

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;localhost&lt;/code&gt; as the host &amp;amp; &lt;code&gt;5433&lt;/code&gt; as the port when adding a new server. Why &lt;code&gt;5433&lt;/code&gt; and &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;5432&lt;/code&gt; - the default port of Postgres? Earlier, we mapped port &lt;code&gt;5432&lt;/code&gt; of the container to port &lt;code&gt;5433&lt;/code&gt; of our &lt;code&gt;localhost&lt;/code&gt;. But, why &lt;code&gt;5433&lt;/code&gt;? It could've &lt;strong&gt;been any port&lt;/strong&gt;, just not &lt;code&gt;5432&lt;/code&gt; because if you have Postgres already installed locally, it is already using port &lt;code&gt;5432&lt;/code&gt;. So, you cannot have the Postgres container also using the same port.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope my explanation was clear &amp;amp; helped you in some way. If you want the source code, you can find the full code &lt;a href="https://github.com/Chandra-Panta-Chhetri/docker-pg-node-ts-tutorial"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Infinite Scroll  With Firebase, React, Intersection Observer &amp; Redux Saga</title>
      <dc:creator>Chandra Panta Chhetri</dc:creator>
      <pubDate>Thu, 07 Jan 2021 00:01:14 +0000</pubDate>
      <link>https://dev.to/chandrapantachhetri/infinite-scroll-pagination-with-firebase-react-intersection-observer-redux-saga-4fd</link>
      <guid>https://dev.to/chandrapantachhetri/infinite-scroll-pagination-with-firebase-react-intersection-observer-redux-saga-4fd</guid>
      <description>&lt;p&gt;While working on a React project with Redux-Saga and Firebase, I wanted to add infinite scrolling to improve site performance and user experience. However, structuring the Firestore, Redux, Redux-Saga, and React code to maximize readability and maintainability was difficult. &lt;/p&gt;

&lt;h3&gt;
  
  
  End Result
&lt;/h3&gt;

&lt;p&gt;We will be building a simple UI that displays 6 products initially and as the user scrolls to the end, we will load 6 more products. Building a simple UI will let us focus on the Redux, Firestore, and Redux-Saga logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1IRUYz5s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hgqhqyol9hp5pe6l0hzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1IRUYz5s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hgqhqyol9hp5pe6l0hzj.png" alt="Trulli" width="880" height="451"&gt;&lt;/a&gt;&lt;br&gt;Figure 1: Loading more products once user scrolls to the end
  &lt;/p&gt;

&lt;p&gt;The code with all configurations can be found at &lt;a href="https://github.com/Chandra-Panta-Chhetri/infinite-scroll-firebase-tutorial"&gt;https://github.com/Chandra-Panta-Chhetri/infinite-scroll-firebase-tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Prerequisite &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of &lt;a href="https://redux.js.org/introduction/getting-started"&gt;Redux&lt;/a&gt;, &lt;a href="https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html"&gt;Redux Saga&lt;/a&gt;, &lt;a href="https://reactjs.org/docs/getting-started.html"&gt;React&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Basis understanding of &lt;a href="https://firebase.google.com/docs/firestore/quickstart"&gt;Firestore&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Basic understanding of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*"&gt;generator functions&lt;/a&gt; as it will be used with Redux Saga&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Redux
&lt;/h3&gt;

&lt;p&gt;To setup the Redux portion, we will need the following &lt;strong&gt;dependencies&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/redux"&gt;Redux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/react-redux"&gt;React-Redux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/redux-saga"&gt;Redux-Saga&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Redux Store, Root Reducer &amp;amp; Root Saga
&lt;/h4&gt;

&lt;p&gt;As with any React, Redux, and Redux-Saga project, the convention is to set up a root reducer, a &lt;a href="https://redux-saga.js.org/docs/advanced/RootSaga.html"&gt;root saga&lt;/a&gt;, and the &lt;a href="https://redux.js.org/api/store"&gt;Redux store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the root reducer, we will combine all the reducers, which in this case will only be a product reducer, and export it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import productReducer from "./product/product.reducer";
import { combineReducers } from "redux";

export default combineReducers({
  product: productReducer
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to the root reducer, in the root saga we will combine all the sagas, which in this case will only be a product saga.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { all, call } from "redux-saga/effects";
import productSagas from "./product/product.sagas";

export default function* rootSaga() {
  yield all([call(productSagas)]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to connect the root saga and root reducer to the Redux store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import createSagaMiddleware from "redux-saga";
import rootReducer from "./root.reducer";
import rootSaga from "./root.saga";
import { createStore, applyMiddleware } from "redux";

const sagaMiddleware = createSagaMiddleware();

const middlewares = [sagaMiddleware];

export const store = createStore(rootReducer, applyMiddleware(...middlewares));

sagaMiddleware.run(rootSaga);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To put it simply, the configuration above connects the root saga to the Redux store by passing the saga middleware to the &lt;code&gt;applyMiddleware&lt;/code&gt; function and then calling the &lt;code&gt;run&lt;/code&gt; method on the saga middleware.&lt;/p&gt;

&lt;p&gt;If you want to understand the configurations in greater depth, refer to &lt;a href="https://www.codementor.io/@rajjeet/step-by-step-how-to-add-redux-saga-to-a-react-redux-app-11xqieyj67"&gt;https://www.codementor.io/@rajjeet/step-by-step-how-to-add-redux-saga-to-a-react-redux-app-11xqieyj67&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When working with Redux, the convention is to define the &lt;a href="https://redux-resource.js.org/api-reference/action-types"&gt;action types&lt;/a&gt;, &lt;a href="https://redux.js.org/understanding/thinking-in-redux/glossary#action-creator"&gt;action creators&lt;/a&gt;, &lt;a href="https://medium.com/@matthew.holman/what-is-a-redux-selector-a517acee1fe8"&gt;selectors&lt;/a&gt;, and a &lt;a href="https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers#writing-reducers"&gt;reducer&lt;/a&gt; so we can manage independent parts of the &lt;a href="https://redux.js.org/api/store"&gt;Redux store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And so, we will create the action types, action creators, selectors, sagas, and a reducer to manage the product states in the Redux store.&lt;/p&gt;

&lt;h4&gt;
  
  
  Product Action Types
&lt;/h4&gt;

&lt;p&gt;Let's start by defining the action types our product reducer and action creators will use. By defining constants, we will have consistent naming in the product reducer and action creators.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const PRODUCT_ACTION_TYPES = {
  START_INITIAL_PRODUCTS_FETCH: "START_INITIAL_PRODUCTS_FETCH",
  INITIAL_PRODUCTS_FETCH_FAIL: "INITIAL_PRODUCTS_FETCH_FAIL",
  INITIAL_PRODUCTS_FETCH_SUCCESS: "INITIAL_PRODUCTS_FETCH_SUCCESS",
  START_LOADING_MORE_PRODUCTS: "START_LOADING_MORE_PRODUCTS",
  LOADING_MORE_PRODUCTS_FAIL: "LOADING_MORE_PRODUCTS_FAIL",
  LOADING_MORE_PRODUCTS_SUCCESS: "LOADING_MORE_PRODUCTS_SUCCESS",
  NO_MORE_PRODUCTS_TO_LOAD: "NO_MORE_PRODUCTS_TO_LOAD"
};

export default PRODUCT_ACTION_TYPES;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are wondering why we are considering the initial product fetch and the subsequent product fetch as different action types, don't worry the reason will become quite clear when we write the sagas and Firestore queries.&lt;/p&gt;

&lt;h4&gt;
  
  
  Product Action Creators
&lt;/h4&gt;

&lt;p&gt;Now that we have defined the action types, we will use them when creating the action creators we will dispatch to update the Redux store.&lt;/p&gt;

&lt;p&gt;For each action type, we will create a function that returns an &lt;a href="https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#actions"&gt;action&lt;/a&gt;. An action is an object of the form &lt;code&gt;{ type, payload }&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import PRODUCT_ACTION_TYPES from "./product.action.types";

export const startInitialProductsFetch = () =&amp;gt; ({
  type: PRODUCT_ACTION_TYPES.START_INITIAL_PRODUCTS_FETCH
});

export const initialProductsFetchFail = (errorMsg) =&amp;gt; ({
  type: PRODUCT_ACTION_TYPES.INITIAL_PRODUCTS_FETCH_FAIL,
  payload: errorMsg
});

export const initialProductsFetchSuccess = (products, lastVisibleDoc) =&amp;gt; ({
  type: PRODUCT_ACTION_TYPES.INITIAL_PRODUCTS_FETCH_SUCCESS,
  payload: { products, lastVisibleDoc }
});

export const startLoadingMoreProducts = () =&amp;gt; ({
  type: PRODUCT_ACTION_TYPES.START_LOADING_MORE_PRODUCTS
});

export const loadingMoreProductsFail = (errorMsg) =&amp;gt; ({
  type: PRODUCT_ACTION_TYPES.LOADING_MORE_PRODUCTS_FAIL,
  payload: errorMsg
});

export const loadingMoreProductsSuccess = (newProducts, lastVisibleDoc) =&amp;gt; ({
  type: PRODUCT_ACTION_TYPES.LOADING_MORE_PRODUCTS_SUCCESS,
  payload: { newProducts, lastVisibleDoc }
});

export const noMoreProductsToLoad = () =&amp;gt; ({
  type: PRODUCT_ACTION_TYPES.NO_MORE_PRODUCTS_TO_LOAD
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Product Reducer
&lt;/h4&gt;

&lt;p&gt;The product reducer will manipulate the following states depending on the action types being dispatched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const INITIAL_STATE = {
  products: [],
  isFetchingProducts: false,
  productsPerPage: 6,
  lastVisibleDoc: null,
  hasMoreToFetch: true
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose of each is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;products&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Stores the product data fetched from Firestore&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isFetchingProducts&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Indicates whether we are fetching products from Firestore&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;productsPerPage&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;The maximum number of products we want to get on each request to Firestore&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lastVisibleDoc&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Stores the last document snapshot from the most recent Firestore request&lt;/li&gt;
&lt;li&gt;When getting the next set of products from Firestore, we need to provide the last document snapshot. We will see an example when we write the Firestore queries later.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hasMoreToFetch&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Indicates whether there are more products to fetch from Firestore (Prevents making requests to Firestore if we have fetched all the products)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can now define the skeleton of the reducer like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import PRODUCT_ACTION_TYPES from "./product.action.types";

const INITIAL_STATE = {
  products: [],
  isFetchingProducts: false,
  productsPerPage: 6,
  lastVisibleDoc: null,
  hasMoreToFetch: true
};

const productReducer = (prevState = INITIAL_STATE, action) =&amp;gt; {
  switch (action.type) {
    default:
      return prevState;
  }
};

export default productReducer;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the action type constants, we can now add case statements so that we can manipulate the state when an action occurs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import PRODUCT_ACTION_TYPES from "./product.action.types";

const INITIAL_STATE = {
  products: [],
  isFetchingProducts: false,
  productsPerPage: 6,
  lastVisibleDoc: null,
  hasMoreToFetch: true
};

const productReducer = (prevState = INITIAL_STATE, action) =&amp;gt; {
  switch (action.type) {
    case PRODUCT_ACTION_TYPES.START_INITIAL_PRODUCTS_FETCH:
      return {
        ...prevState,
        isFetchingProducts: true,
        products: [],
        hasMoreToFetch: true,
        lastVisibleDoc: null
      };
    case PRODUCT_ACTION_TYPES.INITIAL_PRODUCTS_FETCH_FAIL:
    case PRODUCT_ACTION_TYPES.LOADING_MORE_PRODUCTS_FAIL:
    case PRODUCT_ACTION_TYPES.NO_MORE_PRODUCTS_TO_LOAD:
      return {
        ...prevState,
        isFetchingProducts: false,
        hasMoreToFetch: false
      };
    case PRODUCT_ACTION_TYPES.INITIAL_PRODUCTS_FETCH_SUCCESS:
      return {
        ...prevState,
        products: action.payload.products,
        lastVisibleDoc: action.payload.lastVisibleDoc,
        isFetchingProducts: false
      };
    case PRODUCT_ACTION_TYPES.START_LOADING_MORE_PRODUCTS:
      return {
        ...prevState,
        isFetchingProducts: true
      };
    case PRODUCT_ACTION_TYPES.LOADING_MORE_PRODUCTS_SUCCESS:
      return {
        ...prevState,
        isFetchingProducts: false,
        products: [...prevState.products, ...action.payload.newProducts],
        lastVisibleDoc: action.payload.lastVisibleDoc
      };
    default:
      return prevState;
  }
};

export default productReducer;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have implemented the product reducer, based on how the state is being manipulated, it should be more clear as to why we defined the action types we did.&lt;/p&gt;

&lt;h4&gt;
  
  
  Product Selectors
&lt;/h4&gt;

&lt;p&gt;Selectors are functions that accept the entire Redux state as a parameter and return a part of the state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const selectProductsPerPage = (state) =&amp;gt; state.product.productsPerPage;

export const selectLastVisibleDoc = (state) =&amp;gt; state.product.lastVisibleDoc;

export const selectProducts = (state) =&amp;gt; state.product.products;

export const selectIsFetchingProducts = (state) =&amp;gt;
  state.product.isFetchingProducts;

export const selectHasMoreProductsToFetch = (state) =&amp;gt;
  state.product.hasMoreToFetch;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, the &lt;code&gt;selectIsFetchingProducts&lt;/code&gt; selector takes the Redux state and returns the &lt;code&gt;isFetchingProducts&lt;/code&gt; state (the one we set up in the product reducer).&lt;/p&gt;

&lt;h4&gt;
  
  
  Product Sagas
&lt;/h4&gt;

&lt;p&gt;Sagas can be thought of as event listeners as they watch the Redux store for any specified actions and call a specified callback when that action(s) occurs. In the callback, we can perform asynchronous code such as API requests and even dispatch additional actions. &lt;/p&gt;

&lt;p&gt;Let's start by creating 2 sagas - one to watch for the latest "START_INITIAL_PRODUCTS_FETCH" action type and the other for the latest "START_LOADING_MORE_PRODUCTS" action type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import PRODUCT_ACTION_TYPES from "./product.action.types";
import { takeLatest, put, call, all, select } from "redux-saga/effects";

function* watchProductsFetchStart() {
  yield takeLatest(
    PRODUCT_ACTION_TYPES.START_INITIAL_PRODUCTS_FETCH,
    fetchProducts
  );
}

function* watchLoadMoreProducts() {
  yield takeLatest(
    PRODUCT_ACTION_TYPES.START_LOADING_MORE_PRODUCTS,
    fetchMoreProducts
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will define the &lt;code&gt;fetchMoreProducts&lt;/code&gt; and &lt;code&gt;fetchProducts&lt;/code&gt; functions soon.&lt;/p&gt;

&lt;p&gt;To reduce the changes we need to make to the root saga, it is a good practice to create a main saga export instead of exporting each saga (i.e. &lt;code&gt;watchProductsFetchStart&lt;/code&gt; and &lt;code&gt;watchLoadMoreProducts&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import PRODUCT_ACTION_TYPES from "./product.action.types";
import { takeLatest, put, call, all, select } from "redux-saga/effects";

function* watchProductsFetchStart() {
  yield takeLatest(
    PRODUCT_ACTION_TYPES.START_INITIAL_PRODUCTS_FETCH,
    fetchProducts
  );
}

function* watchLoadMoreProducts() {
  yield takeLatest(
    PRODUCT_ACTION_TYPES.START_LOADING_MORE_PRODUCTS,
    fetchMoreProducts
  );
}

export default function* productSagas() {
  yield all([call(watchProductsFetchStart), call(watchLoadMoreProducts)]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create the &lt;code&gt;fetchProducts&lt;/code&gt; function used above, we will import the action creators and selectors we created as we will need to access the Redux state and dispatch actions within &lt;code&gt;fetchProducts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { takeLatest, put, call, all, select } from "redux-saga/effects";
import {
  initialProductsFetchFail,
  initialProductsFetchSuccess,
  noMoreProductsToLoad
} from "./product.actions";
import {
  getProducts
} from "../../firebase-utils/firebase.product_utils";
import {
  selectProductsPerPage
} from "./product.selectors";

function* fetchProducts() {
  try {
    const productsPerPage = yield select(selectProductsPerPage);
    const { products, lastVisibleDoc } = yield getProducts(productsPerPage);
    if (!products.length) {
      return yield put(noMoreProductsToLoad());
    }
    yield put(initialProductsFetchSuccess(products, lastVisibleDoc));
  } catch (err) {
    yield put(
      initialProductsFetchFail("There was a problem displaying the products.")
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the function above, we are getting the &lt;code&gt;productsPerPage&lt;/code&gt; state using the &lt;code&gt;selectProductsPerPage&lt;/code&gt; selector and passing it to &lt;code&gt;getProducts&lt;/code&gt;. Although we have not implemented &lt;code&gt;getProducts&lt;/code&gt; yet, it is evident that it takes the number of products we want to fetch initially and returns an object of the form &lt;code&gt;{ products, lastVisibleDoc }&lt;/code&gt;. If there are no products, we dispatch the &lt;code&gt;noMoreProductsToLoad&lt;/code&gt; action creator, which then changes the &lt;code&gt;hasMoreToFetch&lt;/code&gt; state to &lt;code&gt;true&lt;/code&gt;. Otherwise, we dispatch the &lt;code&gt;initialProductsFetchSuccess&lt;/code&gt; action creator which updates the &lt;code&gt;lastVisibleDoc&lt;/code&gt; and &lt;code&gt;products&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;Now, anytime an action with the type of "START_INITIAL_PRODUCTS_FETCH" is dispatched, the &lt;code&gt;fetchProducts&lt;/code&gt; saga will run and update the Redux store accordingly.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;fetchMoreProducts&lt;/code&gt; function will be similar to &lt;code&gt;fetchProducts&lt;/code&gt; except we will call the &lt;code&gt;getMoreProducts&lt;/code&gt; function and pass it the &lt;code&gt;lastVisibleDoc&lt;/code&gt; and &lt;code&gt;productsPerPage&lt;/code&gt; state. The &lt;code&gt;getMoreProducts&lt;/code&gt; will also be implemented later on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { takeLatest, put, call, all, select } from "redux-saga/effects";
import {
  initialProductsFetchFail,
  initialProductsFetchSuccess,
  loadingMoreProductsFail,
  loadingMoreProductsSuccess,
  noMoreProductsToLoad
} from "./product.actions";
import {
  getProducts,
  getMoreProducts
} from "../../firebase-utils/firebase.product_utils";
import {
  selectProductsPerPage,
  selectLastVisibleDoc
} from "./product.selectors";

function* fetchMoreProducts() {
  try {
    const productsPerPage = yield select(selectProductsPerPage);
    const lastDoc = yield select(selectLastVisibleDoc);
    const { products: newProducts, lastVisibleDoc } = yield getMoreProducts(
      lastDoc,
      productsPerPage
    );
    if (!newProducts.length) {
      return yield put(noMoreProductsToLoad());
    }
    yield put(loadingMoreProductsSuccess(newProducts, lastVisibleDoc));
  } catch (err) {
    yield put(
      loadingMoreProductsFail("There was a problem loading more products.")
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For reference, here is the complete saga code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import PRODUCT_ACTION_TYPES from "./product.action.types";
import { takeLatest, put, call, all, select } from "redux-saga/effects";
import {
  initialProductsFetchFail,
  initialProductsFetchSuccess,
  loadingMoreProductsFail,
  loadingMoreProductsSuccess,
  noMoreProductsToLoad
} from "./product.actions";
import {
  getProducts,
  getMoreProducts
} from "../../firebase-utils/firebase.product_utils";
import {
  selectProductsPerPage,
  selectLastVisibleDoc
} from "./product.selectors";

function* fetchProducts() {
  try {
    const productsPerPage = yield select(selectProductsPerPage);
    const { products, lastVisibleDoc } = yield getProducts(productsPerPage);
    if (!products.length) {
      return yield put(noMoreProductsToLoad());
    }
    yield put(initialProductsFetchSuccess(products, lastVisibleDoc));
  } catch (err) {
    yield put(
      initialProductsFetchFail("There was a problem displaying the products.")
    );
  }
}

function* fetchMoreProducts() {
  try {
    const productsPerPage = yield select(selectProductsPerPage);
    const lastDoc = yield select(selectLastVisibleDoc);
    const { products: newProducts, lastVisibleDoc } = yield getMoreProducts(
      lastDoc,
      productsPerPage
    );
    if (!newProducts.length) {
      return yield put(noMoreProductsToLoad());
    }
    yield put(loadingMoreProductsSuccess(newProducts, lastVisibleDoc));
  } catch (err) {
    yield put(
      loadingMoreProductsFail("There was a problem loading more products.")
    );
  }
}

function* watchProductsFetchStart() {
  yield takeLatest(
    PRODUCT_ACTION_TYPES.START_INITIAL_PRODUCTS_FETCH,
    fetchProducts
  );
}

function* watchLoadMoreProducts() {
  yield takeLatest(
    PRODUCT_ACTION_TYPES.START_LOADING_MORE_PRODUCTS,
    fetchMoreProducts
  );
}

export default function* productSagas() {
  yield all([call(watchProductsFetchStart), call(watchLoadMoreProducts)]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recap&lt;/p&gt;

&lt;p&gt;Now that we are done with the Redux portion, anytime we dispatch the &lt;code&gt;startInitialProductsFetch&lt;/code&gt; and the &lt;code&gt;startLoadingMoreProducts&lt;/code&gt; action creators, the product sagas will call the &lt;code&gt;getProducts&lt;/code&gt; and &lt;code&gt;getMoreProducts&lt;/code&gt; functions and dispatch additional actions to update the product states we defined in the product reducer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firebase Paginated Queries
&lt;/h3&gt;

&lt;p&gt;For this portion, we will need the following &lt;strong&gt;dependency&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/firebase"&gt;Firebase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we can use Firestore, we need to configure Firebase like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import firebase from "firebase/app";
import "firebase/firestore";

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID
};
firebase.initializeApp(firebaseConfig);

export const firestore = firebase.firestore();
export default firebase;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are confused about the configuration above, refer to &lt;a href="https://dev.to/itnext/react-with-firebase-firestore-setup-4ch3"&gt;https://dev.to/itnext/react-with-firebase-firestore-setup-4ch3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We will now implement the &lt;code&gt;getProducts&lt;/code&gt; and &lt;code&gt;getMoreProducts&lt;/code&gt; function we used when we wrote the product sagas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { firestore } from "./firebase.config"; //We exported this earlier in the Firebase configuration
const productCollectionRef = firestore.collection("products");

export const getProducts = async (productsPerPage) =&amp;gt; {
  const paginatedProductsQuery = productCollectionRef
    .orderBy("name", "asc")
    .limit(productsPerPage);
  const productsAndLastVisibleDoc = await excutePaginatedProductQuery(
    paginatedProductsQuery
  );
  return productsAndLastVisibleDoc;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As with any Firestore query, we first need a reference to a &lt;a href="https://firebase.google.com/docs/firestore/data-model#collections"&gt;Firestore collection&lt;/a&gt;. Since we will be using the product collection ref in both &lt;code&gt;getProducts&lt;/code&gt; and &lt;code&gt;getMoreProducts&lt;/code&gt;, we should define it globally.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;getProducts&lt;/code&gt; function, we are querying the product collection and ordering the documents by name in ascending order. Then we are selecting the first &lt;code&gt;productsPerPage&lt;/code&gt; documents. Next, we call &lt;code&gt;excutePaginatedProductQuery&lt;/code&gt;, which takes a paginated query, executes it, returns an object of the form: &lt;code&gt;{ products, lastVisibleDoc }&lt;/code&gt; and then we return this object from &lt;code&gt;getProducts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To improve code reusability, we are creating the &lt;code&gt;excutePaginatedProductQuery&lt;/code&gt; function as the only difference between the &lt;code&gt;getProducts&lt;/code&gt; and &lt;code&gt;getMoreProducts&lt;/code&gt; function is the query we execute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const executePaginatedQuery = async (paginatedQuery) =&amp;gt; {
  const querySnapshot = await paginatedQuery.get();
  const docSnapshots = querySnapshot.docs;
  const lastVisibleDoc = docSnapshots[docSnapshots.length - 1];
  return { lastVisibleDoc, docSnapshots };
};

export const excutePaginatedProductQuery = async (paginatedProductQuery) =&amp;gt; {
  try {
    const {
      lastVisibleDoc,
      docSnapshots: productSnapshots
    } = await executePaginatedQuery(paginatedProductQuery);
    const products = productSnapshots.map((ps) =&amp;gt; ({
      id: ps.id,
      ...ps.data()
    }));
    return { products, lastVisibleDoc };
  } catch (err) {
    return { products: [], lastVisibleDoc: null };
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;executePaginatedProductQuery&lt;/code&gt; function executes a query and returns the products and the last &lt;a href="https://firebase.google.com/docs/reference/node/firebase.firestore.QueryDocumentSnapshot"&gt;document snapshot&lt;/a&gt; from the query result.&lt;/p&gt;

&lt;p&gt;Since we can abstract the process of executing a query, getting the document snapshots, and the last document snapshot, we have moved that logic to the &lt;code&gt;executePaginatedQuery&lt;/code&gt; and called it within the &lt;code&gt;executePaginatedProductQuery&lt;/code&gt; function. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Why do we need the last document snapshot?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many databases have their own ways of skipping documents to get the next documents. In Firestore, we use the &lt;code&gt;startAfter&lt;/code&gt; or &lt;code&gt;startAt&lt;/code&gt; methods and pass a document snapshot to define the starting point for a query. We will see an example shortly.&lt;/p&gt;

&lt;p&gt;So far, we have a function (&lt;code&gt;getProducts&lt;/code&gt;) that queries the product collection and gets the first 6 products. &lt;/p&gt;

&lt;p&gt;To get the next 6 products, we need to another function that uses the &lt;code&gt;startAfter&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getMoreProducts = async (lastVisibleDoc, productsPerPage) =&amp;gt; {
  const nextProductsQuery = productCollectionRef
    .orderBy("name", "asc")
    .startAfter(lastVisibleDoc)
    .limit(productsPerPage);
  const productsAndLastVisibleDoc = await excutePaginatedProductQuery(
    nextProductsQuery
  );
  return productsAndLastVisibleDoc;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From above, it is clear that the &lt;code&gt;getMoreProducts&lt;/code&gt; function is similar to the &lt;code&gt;getProducts&lt;/code&gt; function except for the query. More specifically, the query uses the &lt;code&gt;startAfter&lt;/code&gt; method which skips all the product documents before the &lt;code&gt;lastVisibleDoc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For reference, here is the complete code for this portion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { firestore } from "./firebase.config";
const productCollectionRef = firestore.collection("products");

export const executePaginatedQuery = async (paginatedQuery) =&amp;gt; {
  const querySnapshot = await paginatedQuery.get();
  const docSnapshots = querySnapshot.docs;
  const lastVisibleDoc = docSnapshots[docSnapshots.length - 1];
  return { lastVisibleDoc, docSnapshots };
};

export const excutePaginatedProductQuery = async (paginatedProductQuery) =&amp;gt; {
  try {
    const {
      lastVisibleDoc,
      docSnapshots: productSnapshots
    } = await executePaginatedQuery(paginatedProductQuery);
    const products = productSnapshots.map((ps) =&amp;gt; ({
      id: ps.id,
      ...ps.data()
    }));
    return { products, lastVisibleDoc };
  } catch (err) {
    return { products: [], lastVisibleDoc: null };
  }
};

export const getProducts = async (productsPerPage) =&amp;gt; {
  const paginatedProductsQuery = productCollectionRef
    .orderBy("price")
    .limit(productsPerPage);
  const productsAndLastVisibleDoc = await excutePaginatedProductQuery(
    paginatedProductsQuery
  );
  return productsAndLastVisibleDoc;
};

export const getMoreProducts = async (lastVisibleDoc, productsPerPage) =&amp;gt; {
  const nextProductsQuery = productCollectionRef
    .orderBy("price")
    .startAfter(lastVisibleDoc)
    .limit(productsPerPage);
  const productsAndLastVisibleDoc = await excutePaginatedProductQuery(
    nextProductsQuery
  );
  return productsAndLastVisibleDoc;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recap&lt;/p&gt;

&lt;p&gt;Going back to why we considered the initial product fetch different from the subsequent product fetches, now that we have the &lt;code&gt;getProducts&lt;/code&gt; and &lt;code&gt;getMoreProducts&lt;/code&gt; function, the reason should be more clear. Put simply, when we make the initial request we cannot use the &lt;code&gt;startAfter&lt;/code&gt; method as the last document snapshot is &lt;code&gt;null&lt;/code&gt;. So, we need to make the initial product request, update the &lt;code&gt;lastVisibleDoc&lt;/code&gt; state, and use that when fetching the next products.&lt;/p&gt;

&lt;h3&gt;
  
  
  usePaginationOnIntersection hook
&lt;/h3&gt;

&lt;p&gt;The logic we have implemented so far will only work once the &lt;code&gt;startInitialProductsFetch&lt;/code&gt; and &lt;code&gt;startLoadingMoreProducts&lt;/code&gt; action creators are dispatched.&lt;/p&gt;

&lt;p&gt;We can dispatch the &lt;code&gt;startInitialProductsFetch&lt;/code&gt; action once a component mounts. But for the &lt;code&gt;startLoadingMoreProducts&lt;/code&gt; action, we need to dispatch that each time the user has scrolled to the last product.&lt;/p&gt;

&lt;p&gt;To do that, we can use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API"&gt;Intersection Observer&lt;/a&gt;. The Intersection Observer can run a callback once a specified DOM element appears on the screen.&lt;/p&gt;

&lt;p&gt;In other words, we just need to observe the last product in the &lt;code&gt;products&lt;/code&gt; state and dispatch the &lt;code&gt;startLoadingMoreProducts&lt;/code&gt; action once it appears on the screen. Although we could put this logic in a component, this will reduce code reusability so instead we will create a hook.&lt;/p&gt;

&lt;p&gt;The hook will have the following parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fetchMore&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;a function to call once an DOM element appears on screen &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isFetchingMore&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Indicates whether more products are already being fetched&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hasMoreToFetch&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Indicates whether there are more products to fetch&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;options&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;When creating a new Intersection Observer instance, we can pass an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#creating_an_intersection_observer"&gt;options&lt;/a&gt; object. For example, we can set the &lt;code&gt;threshold&lt;/code&gt; to &lt;code&gt;0.5&lt;/code&gt;, which will trigger the &lt;code&gt;fetchMore&lt;/code&gt; function when the element is 50% visible.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useRef, useCallback } from "react";

const DEFAULT_OPTIONS = { threshold: 0.9 };

const usePaginationOnIntersection = (
  fetchMore,
  isFetchingMore,
  hasMoreToFetch,
  options = DEFAULT_OPTIONS
) =&amp;gt; {
  const observer = useRef();
  const triggerPaginationOnIntersection = useCallback(
    (elementNode) =&amp;gt; {
      if (isFetchingMore) return;
      //Removes the previously observed DOM node before observing another
      if (observer.current) {
        observer.current.disconnect();
      }
      if (!hasMoreToFetch) return;
      observer.current = new IntersectionObserver(([entry]) =&amp;gt; {
        if (entry.isIntersecting) {
          fetchMore();
        }
      }, options);
      if (elementNode) {
        observer.current.observe(elementNode);
      }
    },
    [isFetchingMore, fetchMore, hasMoreToFetch]
  );

  return triggerPaginationOnIntersection;
};

export default usePaginationOnIntersection;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, we are using these hooks from React in the following way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reactjs.org/docs/hooks-reference.html#useref"&gt;&lt;code&gt;useRef&lt;/code&gt;&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;To store a DOM reference to the element we are going to observe&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reactjs.org/docs/hooks-reference.html#usecallback"&gt;&lt;code&gt;useCallback&lt;/code&gt;&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;To return a &lt;a href="https://en.wikipedia.org/wiki/Memoization#:~:text=In%20computing%2C%20memoization%20or%20memoisation,the%20same%20inputs%20occur%20again."&gt;memoized&lt;/a&gt; function for performance reasons. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;triggerPaginationOnIntersection&lt;/code&gt; memoized function attaches a new Intersection Observer to the &lt;code&gt;current&lt;/code&gt; property of the &lt;code&gt;observer&lt;/code&gt; variable. Then it observes the DOM node passed to the function using the &lt;code&gt;observe&lt;/code&gt; method (we can use this because &lt;code&gt;current&lt;/code&gt; property is an Intersection Observer object). Doing this will trigger the &lt;code&gt;fetchMore&lt;/code&gt; function whenever the &lt;code&gt;elementNode&lt;/code&gt; appears on the screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Now the last thing remaining is to get the state from the Redux store so we can display the products and dispatch the actions to fetch products.&lt;/p&gt;

&lt;p&gt;To get the state, we will use the selectors we created earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useEffect } from "react";

import { connect } from "react-redux";
import {
  selectHasMoreProductsToFetch,
  selectIsFetchingProducts,
  selectProducts
} from "./redux/product/product.selectors";
import {
  startInitialProductsFetch
} from "./redux/product/product.actions";

function App({
  products,
  fetchProducts,
  fetchMoreProducts,
  hasMoreProductsToFetch,
  isFetchingProducts
}) {
  useEffect(() =&amp;gt; {
    fetchProducts();
  }, [fetchProducts]);

  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;Products&amp;lt;/h1&amp;gt;
      &amp;lt;div&amp;gt;
        {(products || []).map((product, index) =&amp;gt; (
          &amp;lt;div
            key={product.id}
          &amp;gt;
            &amp;lt;span&amp;gt;Name: {product.name}&amp;lt;/span&amp;gt;
            &amp;lt;span&amp;gt;Price: ${product.price}&amp;lt;/span&amp;gt;
          &amp;lt;/div&amp;gt;
        ))}
        {isFetchingProducts &amp;amp;&amp;amp; &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;}
      &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}

const mapStateToProps = (state) =&amp;gt; ({
  products: selectProducts(state),
  isFetchingProducts: selectIsFetchingProducts(state),
  hasMoreProductsToFetch: selectHasMoreProductsToFetch(state)
});

const mapDispatchToProps = (dispatch) =&amp;gt; ({
  fetchProducts: () =&amp;gt; dispatch(startInitialProductsFetch()),
  fetchMoreProducts: () =&amp;gt; dispatch(startLoadingMoreProducts())
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the component above, we are dispatching the &lt;code&gt;startInitialProductsFetch&lt;/code&gt; action when the component mounts. Consequently, this will run the &lt;code&gt;fetchProducts&lt;/code&gt; and query Firestore for the first 6 products.&lt;/p&gt;

&lt;p&gt;To load more products once the user sees the last product, we can use the &lt;code&gt;usePaginationOnIntersection&lt;/code&gt; hook we created. &lt;/p&gt;

&lt;p&gt;If you remember correctly, the hook returns a memoized function that takes a DOM node as an argument. To pass a DOM node to the function, a shorthand we can use is to &lt;a href="https://reactjs.org/docs/refs-and-the-dom.html#callback-refs"&gt;pass the function to the &lt;code&gt;ref&lt;/code&gt; attribute&lt;/a&gt; if it is the last product in the &lt;code&gt;products&lt;/code&gt; state (we only want to fetch more products once the user sees the last product).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useEffect } from "react";

import { connect } from "react-redux";
import {
  selectHasMoreProductsToFetch,
  selectIsFetchingProducts,
  selectProducts
} from "./redux/product/product.selectors";
import {
  startInitialProductsFetch,
  startLoadingMoreProducts
} from "./redux/product/product.actions";
import usePaginationOnIntersection from "./hooks/usePaginationOnIntersection.hook";

function App({
  products,
  fetchProducts,
  fetchMoreProducts,
  hasMoreProductsToFetch,
  isFetchingProducts
}) {
  const fetchMoreOnIntersection = usePaginationOnIntersection(
    fetchMoreProducts,
    isFetchingProducts,
    hasMoreProductsToFetch
  );

  useEffect(() =&amp;gt; {
    fetchProducts();
  }, [fetchProducts]);

  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;Products&amp;lt;/h1&amp;gt;
      &amp;lt;div&amp;gt;
        {(products || []).map((product, index) =&amp;gt; (
          &amp;lt;div
            key={product.id}
            ref={
              index + 1 === products.length
                ? fetchMoreOnIntersection
                : undefined
            }
          &amp;gt;
            &amp;lt;span&amp;gt;Name: {product.name}&amp;lt;/span&amp;gt;
            &amp;lt;span&amp;gt;Price: ${product.price}&amp;lt;/span&amp;gt;
          &amp;lt;/div&amp;gt;
        ))}
        {isFetchingProducts &amp;amp;&amp;amp; &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;}
      &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}

const mapStateToProps = (state) =&amp;gt; ({
  products: selectProducts(state),
  isFetchingProducts: selectIsFetchingProducts(state),
  hasMoreProductsToFetch: selectHasMoreProductsToFetch(state)
});

const mapDispatchToProps = (dispatch) =&amp;gt; ({
  fetchProducts: () =&amp;gt; dispatch(startInitialProductsFetch()),
  fetchMoreProducts: () =&amp;gt; dispatch(startLoadingMoreProducts())
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now anytime the user scrolls to the last product, the following events will happen if &lt;code&gt;hasMoreToFetch&lt;/code&gt; is true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;startLoadingMoreProducts&lt;/code&gt; action will be dispatched&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;products&lt;/code&gt; state in Redux store will update&lt;/li&gt;
&lt;li&gt;Component will re-render&lt;/li&gt;
&lt;li&gt;A new Intersection Observer will be attached to last product and the previous observed element will be removed&lt;/li&gt;
&lt;li&gt;Steps 1-4 will be repeated until &lt;code&gt;hasMoreToFetch&lt;/code&gt; is false&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>react</category>
      <category>redux</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Responsive React File Upload Component With Drag And Drop</title>
      <dc:creator>Chandra Panta Chhetri</dc:creator>
      <pubDate>Sun, 03 Jan 2021 04:44:29 +0000</pubDate>
      <link>https://dev.to/chandrapantachhetri/responsive-react-file-upload-component-with-drag-and-drop-4ef8</link>
      <guid>https://dev.to/chandrapantachhetri/responsive-react-file-upload-component-with-drag-and-drop-4ef8</guid>
      <description>&lt;p&gt;While working on a React project, I implemented a &lt;strong&gt;responsive file upload component&lt;/strong&gt; that supports &lt;strong&gt;drag and drop without using any libraries&lt;/strong&gt;. Most of the file upload components online used libraries such as &lt;a href="https://www.npmjs.com/package/react-dropzone" rel="noopener noreferrer"&gt;react-dropzone&lt;/a&gt; to support drag and drop. So, I thought I'd share how I made the component and show a typical use case for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  End result
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx2kisq8adak60yso498d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx2kisq8adak60yso498d.png" width="800" height="364"&gt;&lt;/a&gt;Figure 1: File upload component used in a form (example use case)  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F028yeaiio875k7r4mv5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F028yeaiio875k7r4mv5t.png" width="371" height="749"&gt;&lt;/a&gt;Figure 2: Responsive file upload component  &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;features&lt;/strong&gt; include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;drag and drop without using any libraries&lt;/li&gt;
&lt;li&gt;displaying image preview for image files&lt;/li&gt;
&lt;li&gt;displaying file size &amp;amp; name&lt;/li&gt;
&lt;li&gt;removing files in the "To Upload" section&lt;/li&gt;
&lt;li&gt;preventing user from uploading files bigger than a specified size

&lt;ul&gt;
&lt;li&gt;Note: this should also be done on the backend for security reasons&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Setup
&lt;/h3&gt;

&lt;p&gt;Prerequisite: &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node&lt;/a&gt; (for installing npm packages) &lt;/p&gt;

&lt;p&gt;If you are familiar with building React applications, the easiest way to set up a new React project is by using &lt;a href="https://github.com/facebook/create-react-app" rel="noopener noreferrer"&gt;create-react-app&lt;/a&gt;. So, run the following commands in a terminal/command-line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-react-app react-file-upload
cd react-file-upload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ensure everything was set up properly after you run &lt;code&gt;npm start&lt;/code&gt;, the following should appear once you visit &lt;code&gt;localhost:3000&lt;/code&gt; in a browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbvd8uirtbb2d0pitprov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbvd8uirtbb2d0pitprov.png" alt="Alt Text" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before building the component, let's modify and remove some files to get rid of unnecessary code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change &lt;code&gt;App.js&lt;/code&gt; to the following:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';

function App() {
  return (
    &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Change &lt;code&gt;index.js&lt;/code&gt; to the following:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;,
  document.getElementById('root')
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove all files in the &lt;code&gt;src&lt;/code&gt; folder except&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;App.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.js&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;index.css&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  File Upload Component
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Installing Dependencies
&lt;/h4&gt;

&lt;p&gt;The dependencies we will need are: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/styled-components" rel="noopener noreferrer"&gt;styled-components&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For styling the component

&lt;ul&gt;
&lt;li&gt;styled components allow for style encapsulation and creating 
dynamic styles via props &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/node-sass" rel="noopener noreferrer"&gt;node-sass&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For compiling Sass styles used in styled components (Optional, can use CSS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To install them, run &lt;code&gt;npm i styled-components node-sass&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Folder Structure
&lt;/h4&gt;

&lt;p&gt;A good convention for structuring folders and files is to create a components folder that has a folder for each component.  This makes it easier to find the logic and styles for each component. &lt;/p&gt;

&lt;p&gt;Following this convention, &lt;strong&gt;create a components folder&lt;/strong&gt; in the &lt;code&gt;src&lt;/code&gt; folder and then a &lt;strong&gt;file-upload folder&lt;/strong&gt; within the &lt;code&gt;components&lt;/code&gt; folder. &lt;/p&gt;

&lt;p&gt;Lastly, within the file-upload folder, create 2 new files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;file-upload.component.jsx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;file-upload.styles.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  State
&lt;/h4&gt;

&lt;p&gt;Since we are creating a functional component and need to use state, we will use the &lt;a href="https://reactjs.org/docs/hooks-reference.html#usestate" rel="noopener noreferrer"&gt;useState hook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The useState hook returns a stateful value which is the same as the value passed as the first argument, and a function to update it.&lt;/p&gt;

&lt;p&gt;For our purposes, we will need state to keep track of the uploaded files. So, in the &lt;code&gt;file-upload.component.jsx&lt;/code&gt; file, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from "react";

const FileUpload = () =&amp;gt; {
  const [files, setFiles] = useState({});

  return (
   &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
  )
}

export default FileUpload;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;“Shouldn't we use an empty array instead of an empty object for the &lt;code&gt;files&lt;/code&gt; state?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using an object will allow us to easily manipulate (add/remove) the &lt;code&gt;files&lt;/code&gt; state and prevent files with the same name from being uploaded more than once. Here is an example of how the &lt;code&gt;files&lt;/code&gt; state will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "file1.png": File,
 "file2.png": File
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we used an array it would require more work. For instance, to remove a file we would have to iterate through each file until we find the one to remove.&lt;/p&gt;

&lt;p&gt;Note: File is a JS object. More info can be found at &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/API/File&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  useRef hook
&lt;/h4&gt;

&lt;p&gt;If you look at Figure 1 above, you will notice the user can either drag and drop files or press the Upload Files button. By default, an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file" rel="noopener noreferrer"&gt;file input tag&lt;/a&gt; will open the file explorer once it is clicked. However, we want to open it once the Upload Files button is clicked so we will require a DOM reference to the file input tag.&lt;/p&gt;

&lt;p&gt;To create a DOM reference, we will use the &lt;a href="https://reactjs.org/docs/hooks-reference.html#useref" rel="noopener noreferrer"&gt;useRef hook&lt;/a&gt;. The useRef hook returns a mutable ref object whose &lt;code&gt;.current&lt;/code&gt; property refers to a DOM node (file input tag in this case).&lt;/p&gt;

&lt;p&gt;Once we use the useRef hook, we must pass the returned value to the ref attribute of the file input tag, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useRef } from "react";

const FileUpload = (props) =&amp;gt; {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  return (
   &amp;lt;input type="file" ref={fileInputField} /&amp;gt;
  )
}

export default FileUpload;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Props
&lt;/h4&gt;

&lt;p&gt;The component will have the following props:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;label&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Determines the label of the component (e.g. "Profile Image(s)" in Figure 1 above)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;maxFileSizeInBytes&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;For preventing files above the specified size from being uploaded&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;updateFilesCb&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;A callback function used for sending the &lt;code&gt;files&lt;/code&gt; state to the parent component&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;“Why do we need to send the &lt;code&gt;files&lt;/code&gt; state to the parent component?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Typically, the file upload component will be used in a form and when working with forms in React, the component stores the form data in the state. Thus, for the parent component to also store the uploaded files, we need the file upload component to send it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Why do we need use a callback function to send the &lt;code&gt;files&lt;/code&gt; state to the parent component?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since React has unidirectional data flow, we cannot easily pass data from the child component (file upload component) to the parent component. As a workaround, we will pass a function declared in the parent component and the file upload component will call that function with the &lt;code&gt;files&lt;/code&gt; state as an argument. This process of sending data from the child to the parent can be further explained at &lt;a href="https://medium.com/@jasminegump/passing-data-between-a-parent-and-child-in-react-deea2ec8e654" rel="noopener noreferrer"&gt;https://medium.com/@jasminegump/passing-data-between-a-parent-and-child-in-react-deea2ec8e654&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment" rel="noopener noreferrer"&gt;destructuring&lt;/a&gt;, we can now add the props like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useRef, useState } from "react";

const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) =&amp;gt; {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  return (
   &amp;lt;input type="file" ref={fileInputField} /&amp;gt;
  )
}

export default FileUpload;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;“Why are we using the spread syntax when destructuring &lt;code&gt;otherProps&lt;/code&gt;?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When destructuring, we can assign all other values that were not explicitly destructured to a variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let props = { a: 1, b: 2, c: 3};
let {a, ...otherProps} = props;

//a = 1
//otherProps = {b: 2, c: 3};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, for any props that we do not destructure, they will be assigned to the &lt;code&gt;otherProps&lt;/code&gt; variable. We will see the use of this &lt;code&gt;otherProps&lt;/code&gt; variable later.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML
&lt;/h4&gt;

&lt;p&gt;For the icons shown in Figure 1, we will be using Font Awesome. To import it, add the following in the &lt;a href="https://www.w3schools.com/tags/tag_head.asp#:~:text=The%20element%20is%20a,scripts%2C%20and%20other%20meta%20information." rel="noopener noreferrer"&gt;head tag&lt;/a&gt; in the &lt;code&gt;public/index.html&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link
 rel="stylesheet"
 href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From Figure 1, it is evident we can split the HTML for the component into 2 main parts. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk3pfntgvbqewsd2w0mpn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk3pfntgvbqewsd2w0mpn.png" alt="Main parts of component" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the component with the HTML for the first part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useRef, useState } from "react";

const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) =&amp;gt; {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  return (
      &amp;lt;section&amp;gt;
        &amp;lt;label&amp;gt;{label}&amp;lt;/label&amp;gt;
        &amp;lt;p&amp;gt;Drag and drop your files anywhere or&amp;lt;/p&amp;gt;
        &amp;lt;button type="button"&amp;gt;
          &amp;lt;i className="fas fa-file-upload" /&amp;gt;
          &amp;lt;span&amp;gt; Upload {otherProps.multiple ? "files" : "a file"}&amp;lt;/span&amp;gt;
        &amp;lt;/button&amp;gt;
        &amp;lt;input
          type="file"
          ref={fileInputField}
          title=""
          value=""
          {...otherProps}
        /&amp;gt;
      &amp;lt;/section&amp;gt;      
  );
}

export default FileUpload;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Earlier, we discussed that any props that we don't destructure will be assigned to the &lt;code&gt;otherProps&lt;/code&gt; variable (i.e. any prop other than &lt;code&gt;label&lt;/code&gt;, &lt;code&gt;updateFilesCb&lt;/code&gt;, &lt;code&gt;maxFileSizeInBytes&lt;/code&gt;). In the code above, we are taking that &lt;code&gt;otherProps&lt;/code&gt; variable and passing it to the file input tag. This was done so that we can add &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file" rel="noopener noreferrer"&gt;attributes&lt;/a&gt; to the file input tag from the parent component via props. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Why are we setting the title and value attribute to &lt;code&gt;""&lt;/code&gt;?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Setting the title attribute to &lt;code&gt;""&lt;/code&gt; gets rid of the text that shows up by default when hovering over the input tag ("No file chosen").&lt;/p&gt;

&lt;p&gt;Setting the value attribute to &lt;code&gt;""&lt;/code&gt; fixes an edge case where uploading a file right after removing it does not change the &lt;code&gt;files&lt;/code&gt; state. Later, we will see that the &lt;code&gt;files&lt;/code&gt; state only changes once the value of the input tag changes. This bug occurs because when we remove a file, the input tag's value does not change. Since state changes re-render HTML, setting the value attribute to &lt;code&gt;""&lt;/code&gt; resets the input tag's value on each &lt;code&gt;files&lt;/code&gt; state change.&lt;/p&gt;

&lt;p&gt;Before we write the HTML for the second part, keep in mind that React only allows for &lt;a href="https://reactjs.org/docs/fragments.html#short-syntax" rel="noopener noreferrer"&gt;returning one parent element&lt;/a&gt; from a component. Thus, we will enclose both parts in a &lt;code&gt;&amp;lt;&amp;gt;&amp;lt;/&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;Here is the component with the HTML for both parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useRef, useState } from "react";

const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const KILO_BYTES_PER_BYTE = 1000;

const convertBytesToKB = (bytes) =&amp;gt; Math.round(bytes / KILO_BYTES_PER_BYTE);

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) =&amp;gt; {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  return (
    &amp;lt;&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;label&amp;gt;{label}&amp;lt;/label&amp;gt;
        &amp;lt;p&amp;gt;Drag and drop your files anywhere or&amp;lt;/p&amp;gt;
        &amp;lt;button type="button"&amp;gt;
          &amp;lt;i className="fas fa-file-upload" /&amp;gt;
          &amp;lt;span&amp;gt; Upload {otherProps.multiple ? "files" : "a file"}&amp;lt;/span&amp;gt;
        &amp;lt;/button&amp;gt;
        &amp;lt;input
          type="file"
          ref={fileInputField}
          title=""
          value=""
          {...otherProps}
        /&amp;gt;
      &amp;lt;/section&amp;gt;

      {/*second part starts here*/}
      &amp;lt;article&amp;gt;
        &amp;lt;span&amp;gt;To Upload&amp;lt;/span&amp;gt;
        &amp;lt;section&amp;gt;
          {Object.keys(files).map((fileName, index) =&amp;gt; {
            let file = files[fileName];
            let isImageFile = file.type.split("/")[0] === "image";
            return (
              &amp;lt;section key={fileName}&amp;gt;
                &amp;lt;div&amp;gt;
                  {isImageFile &amp;amp;&amp;amp; (
                    &amp;lt;img
                      src={URL.createObjectURL(file)}
                      alt={`file preview ${index}`}
                    /&amp;gt;
                  )}
                  &amp;lt;div isImageFile={isImageFile}&amp;gt;
                    &amp;lt;span&amp;gt;{file.name}&amp;lt;/span&amp;gt;
                    &amp;lt;aside&amp;gt;
                      &amp;lt;span&amp;gt;{convertBytesToKB(file.size)} kb&amp;lt;/span&amp;gt;
                      &amp;lt;i className="fas fa-trash-alt" /&amp;gt;
                    &amp;lt;/aside&amp;gt;
                  &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
              &amp;lt;/section&amp;gt;
            );
          })}
        &amp;lt;/section&amp;gt;
      &amp;lt;/article&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default FileUpload;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the second part of the HTML, we are iterating through each file in the &lt;code&gt;files&lt;/code&gt; state and displaying the file name, size in KB, and an image preview if the file type is &lt;code&gt;image/*&lt;/code&gt; (i.e. png, jpg...etc).&lt;/p&gt;

&lt;p&gt;To display an image preview, we are using the &lt;code&gt;URL.createObjectURL&lt;/code&gt; function. The createObjectURL function takes an object, which in this case is a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;File&lt;/a&gt; object, and returns a temporary URL for accessing the file. We can then set that URL to &lt;code&gt;src&lt;/code&gt; attribute of an img tag.&lt;/p&gt;

&lt;h4&gt;
  
  
  Styling
&lt;/h4&gt;

&lt;p&gt;We will now use the styled-components package we installed earlier.&lt;/p&gt;

&lt;p&gt;Add the following in the &lt;code&gt;file-upload.styles.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import styled from "styled-components";

export const FileUploadContainer = styled.section`
  position: relative;
  margin: 25px 0 15px;
  border: 2px dotted lightgray;
  padding: 35px 20px;
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: white;
`;

export const FormField = styled.input`
  font-size: 18px;
  display: block;
  width: 100%;
  border: none;
  text-transform: none;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  opacity: 0;

  &amp;amp;:focus {
    outline: none;
  }
`;

export const InputLabel = styled.label`
  top: -21px;
  font-size: 13px;
  color: black;
  left: 0;
  position: absolute;
`;

export const DragDropText = styled.p`
  font-weight: bold;
  letter-spacing: 2.2px;
  margin-top: 0;
  text-align: center;
`;

export const UploadFileBtn = styled.button`
  box-sizing: border-box;
  appearance: none;
  background-color: transparent;
  border: 2px solid #3498db;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 1.1em 2.8em;
  text-align: center;
  text-transform: uppercase;
  font-weight: 700;
  border-radius: 6px;
  color: #3498db;
  position: relative;
  overflow: hidden;
  z-index: 1;
  transition: color 250ms ease-in-out;
  font-family: "Open Sans", sans-serif;
  width: 45%;
  display: flex;
  align-items: center;
  padding-right: 0;
  justify-content: center;

  &amp;amp;:after {
    content: "";
    position: absolute;
    display: block;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    height: 100%;
    background: #3498db;
    z-index: -1;
    transition: width 250ms ease-in-out;
  }

  i {
    font-size: 22px;
    margin-right: 5px;
    border-right: 2px solid;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    width: 20%;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  @media only screen and (max-width: 500px) {
    width: 70%;
  }

  @media only screen and (max-width: 350px) {
    width: 100%;
  }

  &amp;amp;:hover {
    color: #fff;
    outline: 0;
    background: transparent;

    &amp;amp;:after {
      width: 110%;
    }
  }

  &amp;amp;:focus {
    outline: 0;
    background: transparent;
  }

  &amp;amp;:disabled {
    opacity: 0.4;
    filter: grayscale(100%);
    pointer-events: none;
  }
`;

export const FilePreviewContainer = styled.article`
  margin-bottom: 35px;

  span {
    font-size: 14px;
  }
`;

export const PreviewList = styled.section`
  display: flex;
  flex-wrap: wrap;
  margin-top: 10px;

  @media only screen and (max-width: 400px) {
    flex-direction: column;
  }
`;

export const FileMetaData = styled.div`
  display: ${(props) =&amp;gt; (props.isImageFile ? "none" : "flex")};
  flex-direction: column;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 10px;
  border-radius: 6px;
  color: white;
  font-weight: bold;
  background-color: rgba(5, 5, 5, 0.55);

  aside {
    margin-top: auto;
    display: flex;
    justify-content: space-between;
  }
`;

export const RemoveFileIcon = styled.i`
  cursor: pointer;

  &amp;amp;:hover {
    transform: scale(1.3);
  }
`;

export const PreviewContainer = styled.section`
  padding: 0.25rem;
  width: 20%;
  height: 120px;
  border-radius: 6px;
  box-sizing: border-box;

  &amp;amp;:hover {
    opacity: 0.55;

    ${FileMetaData} {
      display: flex;
    }
  }

  &amp;amp; &amp;gt; div:first-of-type {
    height: 100%;
    position: relative;
  }

  @media only screen and (max-width: 750px) {
    width: 25%;
  }

  @media only screen and (max-width: 500px) {
    width: 50%;
  }

  @media only screen and (max-width: 400px) {
    width: 100%;
    padding: 0 0 0.4em;
  }
`;

export const ImagePreview = styled.img`
  border-radius: 6px;
  width: 100%;
  height: 100%;
`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using styled-components, we are creating components that render an HTML tag with some styles. For example, the &lt;code&gt;ImagePreview&lt;/code&gt; is a component that renders an &lt;code&gt;img&lt;/code&gt; tag with the styles inside the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" rel="noopener noreferrer"&gt;tagged template literal&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Since we are creating components, we can pass props to it and access it when writing the styles (e.g. &lt;code&gt;FileMetaData&lt;/code&gt; in the example above). &lt;/p&gt;

&lt;p&gt;We have now finished the styling and adding drag and drop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“But wait, when did we add drag and drop?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, the file input tag supports drag and drop. We simply just styled the input tag and made it &lt;a href="https://www.w3schools.com/css/css_positioning.asp" rel="noopener noreferrer"&gt;absolutely positioned&lt;/a&gt; (refer to &lt;code&gt;FormField&lt;/code&gt; above). &lt;/p&gt;

&lt;p&gt;To use the styles we wrote, import all the styled components and replace the HTML in the &lt;code&gt;file-upload.component.jsx&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useRef, useState } from "react";
import {
  FileUploadContainer,
  FormField,
  DragDropText,
  UploadFileBtn,
  FilePreviewContainer,
  ImagePreview,
  PreviewContainer,
  PreviewList,
  FileMetaData,
  RemoveFileIcon,
  InputLabel
} from "./file-upload.styles";

const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const KILO_BYTES_PER_BYTE = 1000;

const convertBytesToKB = (bytes) =&amp;gt;
  Math.round(bytes / KILO_BYTES_PER_BYTE);

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) =&amp;gt; {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

    return (
    &amp;lt;&amp;gt;
      &amp;lt;FileUploadContainer&amp;gt;
        &amp;lt;InputLabel&amp;gt;{label}&amp;lt;/InputLabel&amp;gt;
        &amp;lt;DragDropText&amp;gt;Drag and drop your files anywhere or&amp;lt;/DragDropText&amp;gt;
        &amp;lt;UploadFileBtn type="button"&amp;gt;
          &amp;lt;i className="fas fa-file-upload" /&amp;gt;
          &amp;lt;span&amp;gt; Upload {otherProps.multiple ? "files" : "a file"}&amp;lt;/span&amp;gt;
        &amp;lt;/UploadFileBtn&amp;gt;
        &amp;lt;FormField
          type="file"
          ref={fileInputField}
          title=""
          value=""
          {...otherProps}
        /&amp;gt;
      &amp;lt;/FileUploadContainer&amp;gt;
      &amp;lt;FilePreviewContainer&amp;gt;
        &amp;lt;span&amp;gt;To Upload&amp;lt;/span&amp;gt;
        &amp;lt;PreviewList&amp;gt;
          {Object.keys(files).map((fileName, index) =&amp;gt; {
            let file = files[fileName];
            let isImageFile = file.type.split("/")[0] === "image";
            return (
              &amp;lt;PreviewContainer key={fileName}&amp;gt;
                &amp;lt;div&amp;gt;
                  {isImageFile &amp;amp;&amp;amp; (
                    &amp;lt;ImagePreview
                      src={URL.createObjectURL(file)}
                      alt={`file preview ${index}`}
                    /&amp;gt;
                  )}
                  &amp;lt;FileMetaData isImageFile={isImageFile}&amp;gt;
                    &amp;lt;span&amp;gt;{file.name}&amp;lt;/span&amp;gt;
                    &amp;lt;aside&amp;gt;
                      &amp;lt;span&amp;gt;{convertBytesToKB(file.size)} kb&amp;lt;/span&amp;gt;
                      &amp;lt;RemoveFileIcon
                        className="fas fa-trash-alt"
                      /&amp;gt;
                    &amp;lt;/aside&amp;gt;
                  &amp;lt;/FileMetaData&amp;gt;
                &amp;lt;/div&amp;gt;
              &amp;lt;/PreviewContainer&amp;gt;
            );
          })}
        &amp;lt;/PreviewList&amp;gt;
      &amp;lt;/FilePreviewContainer&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default FileUpload;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Functionality
&lt;/h4&gt;

&lt;p&gt;We are almost finished with the file-upload component, we just need to add functions so that &lt;code&gt;files&lt;/code&gt; state can be modified.&lt;/p&gt;

&lt;p&gt;Earlier we created a DOM reference using the useRef hook. We will now use that to open the file explorer once the "Upload Files" button is clicked. To do this, add the following function within the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleUploadBtnClick = () =&amp;gt; {
  fileInputField.current.click();
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to add an &lt;code&gt;onClick&lt;/code&gt; attribute to the &lt;code&gt;UploadFileBtn&lt;/code&gt; component to trigger the function above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;UploadFileBtn type="button" onClick={handleUploadBtnClick}&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To process the files selected by the user once the "Upload Files" button is clicked, we need to add an &lt;code&gt;onChange&lt;/code&gt; attribute to the &lt;code&gt;FormField&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;FormField
  type="file"
  ref={fileInputField}
  onChange={handleNewFileUpload}
  title=""
  value=""
  {...otherProps}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like with any DOM event (e.g. &lt;code&gt;onClick&lt;/code&gt;), the function to handle the event will have access to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Event" rel="noopener noreferrer"&gt;event&lt;/a&gt; object. So, the &lt;code&gt;handleNewFileUpload&lt;/code&gt; function will have the event object as its first parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const handleNewFileUpload = (e) =&amp;gt; {
    const { files: newFiles } = e.target;
    if (newFiles.length) {
      let updatedFiles = addNewFiles(newFiles);
      setFiles(updatedFiles);
      callUpdateFilesCb(updatedFiles);
    }
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the function above, we access the files selected by the user from the &lt;code&gt;e.target.files&lt;/code&gt; property then pass it to a function called &lt;code&gt;addNewFiles&lt;/code&gt;. Then, we take the return value from &lt;code&gt;addNewFiles&lt;/code&gt; and pass it to the &lt;code&gt;setFiles&lt;/code&gt; to update the &lt;code&gt;files&lt;/code&gt; state. Since any changes to the &lt;code&gt;files&lt;/code&gt; state must be sent to the parent component, we need to call the &lt;code&gt;callUpdateFilesCb&lt;/code&gt; function. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;addNewFiles&lt;/code&gt; function takes a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FileList" rel="noopener noreferrer"&gt;FileList&lt;/a&gt; object (&lt;code&gt;e.target.files&lt;/code&gt; above returns a FileList), iterates through it, and returns an object where the key is the file name and the value is the File object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const addNewFiles = (newFiles) =&amp;gt; {
    for (let file of newFiles) {
      if (file.size &amp;lt;= maxFileSizeInBytes) {
        if (!otherProps.multiple) {
          return { file };
        }
        files[file.name] = file;
      }
    }
    return { ...files };
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;“Why are checking if there is not a &lt;code&gt;multiple&lt;/code&gt; property in &lt;code&gt;otherProps&lt;/code&gt;?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As explained earlier, we are using the &lt;code&gt;otherProps&lt;/code&gt; variable to add attributes to the file input tag. So, if we don't pass a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-multiple" rel="noopener noreferrer"&gt;&lt;code&gt;multiple&lt;/code&gt;&lt;/a&gt; prop to the file upload component, then the file input tag does not allow for selecting multiple files. Put simply, if there is a &lt;code&gt;multiple&lt;/code&gt; prop, selected files will get added to the &lt;code&gt;files&lt;/code&gt; state. Otherwise, selecting a new file will remove the previous &lt;code&gt;files&lt;/code&gt; state and replace it with the newly selected file. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;callUpdateFilesCb&lt;/code&gt; function takes the value returned from &lt;code&gt;addNewFiles&lt;/code&gt;, converts the &lt;code&gt;files&lt;/code&gt; state to an array and calls the &lt;code&gt;updateFilesCb&lt;/code&gt; function (from the props). &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Why do we pass &lt;code&gt;updatedFiles&lt;/code&gt; to &lt;code&gt;callUpdateFilesCb&lt;/code&gt; when we could just use the &lt;code&gt;files&lt;/code&gt; state within the function?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since React state updates are asynchronous, there is no guarantee that when the &lt;code&gt;callUpdateFilesCb&lt;/code&gt; gets called, the &lt;code&gt;files&lt;/code&gt; state will have changed. &lt;/p&gt;

&lt;p&gt;"Why do we have to convert the &lt;code&gt;files&lt;/code&gt; state to an array?"&lt;/p&gt;

&lt;p&gt;We don't! However, when uploading files in the &lt;code&gt;files&lt;/code&gt; state to some third party service (e.g. Firebase Cloud Storage), it's easier to work with arrays.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const convertNestedObjectToArray = (nestedObj) =&amp;gt;
  Object.keys(nestedObj).map((key) =&amp;gt; nestedObj[key]);

const callUpdateFilesCb = (files) =&amp;gt; {
  const filesAsArray = convertNestedObjectToArray(files);
  updateFilesCb(filesAsArray);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To remove a file, we first need to add an &lt;code&gt;onClick&lt;/code&gt; attribute to the &lt;code&gt;RemoveFileIcon&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;RemoveFileIcon
  className="fas fa-trash-alt"
  onClick={() =&amp;gt; removeFile(fileName)}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;removeFile&lt;/code&gt; function will take a file name, delete it from the &lt;code&gt;files&lt;/code&gt; state, update the &lt;code&gt;files&lt;/code&gt; state, and inform the parent component of the changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const removeFile = (fileName) =&amp;gt; {
  delete files[fileName];
  setFiles({ ...files });
  callUpdateFilesCb({ ...files });
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the component with all the functions above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useRef, useState } from "react";
import {
  FileUploadContainer,
  FormField,
  DragDropText,
  UploadFileBtn,
  FilePreviewContainer,
  ImagePreview,
  PreviewContainer,
  PreviewList,
  FileMetaData,
  RemoveFileIcon,
  InputLabel
} from "./file-upload.styles";

const KILO_BYTES_PER_BYTE = 1000;
const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;

const convertNestedObjectToArray = (nestedObj) =&amp;gt;
  Object.keys(nestedObj).map((key) =&amp;gt; nestedObj[key]);

const convertBytesToKB = (bytes) =&amp;gt; Math.round(bytes / KILO_BYTES_PER_BYTE);

const FileUpload = ({
  label,
  updateFilesCb,
  maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
  ...otherProps
}) =&amp;gt; {
  const fileInputField = useRef(null);
  const [files, setFiles] = useState({});

  const handleUploadBtnClick = () =&amp;gt; {
    fileInputField.current.click();
  };

  const addNewFiles = (newFiles) =&amp;gt; {
    for (let file of newFiles) {
      if (file.size &amp;lt; maxFileSizeInBytes) {
        if (!otherProps.multiple) {
          return { file };
        }
        files[file.name] = file;
      }
    }
    return { ...files };
  };

  const callUpdateFilesCb = (files) =&amp;gt; {
    const filesAsArray = convertNestedObjectToArray(files);
    updateFilesCb(filesAsArray);
  };

  const handleNewFileUpload = (e) =&amp;gt; {
    const { files: newFiles } = e.target;
    if (newFiles.length) {
      let updatedFiles = addNewFiles(newFiles);
      setFiles(updatedFiles);
      callUpdateFilesCb(updatedFiles);
    }
  };

  const removeFile = (fileName) =&amp;gt; {
    delete files[fileName];
    setFiles({ ...files });
    callUpdateFilesCb({ ...files });
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;FileUploadContainer&amp;gt;
        &amp;lt;InputLabel&amp;gt;{label}&amp;lt;/InputLabel&amp;gt;
        &amp;lt;DragDropText&amp;gt;Drag and drop your files anywhere or&amp;lt;/DragDropText&amp;gt;
        &amp;lt;UploadFileBtn type="button" onClick={handleUploadBtnClick}&amp;gt;
          &amp;lt;i className="fas fa-file-upload" /&amp;gt;
          &amp;lt;span&amp;gt; Upload {otherProps.multiple ? "files" : "a file"}&amp;lt;/span&amp;gt;
        &amp;lt;/UploadFileBtn&amp;gt;
        &amp;lt;FormField
          type="file"
          ref={fileInputField}
          onChange={handleNewFileUpload}
          title=""
          value=""
          {...otherProps}
        /&amp;gt;
      &amp;lt;/FileUploadContainer&amp;gt;
      &amp;lt;FilePreviewContainer&amp;gt;
        &amp;lt;span&amp;gt;To Upload&amp;lt;/span&amp;gt;
        &amp;lt;PreviewList&amp;gt;
          {Object.keys(files).map((fileName, index) =&amp;gt; {
            let file = files[fileName];
            let isImageFile = file.type.split("/")[0] === "image";
            return (
              &amp;lt;PreviewContainer key={fileName}&amp;gt;
                &amp;lt;div&amp;gt;
                  {isImageFile &amp;amp;&amp;amp; (
                    &amp;lt;ImagePreview
                      src={URL.createObjectURL(file)}
                      alt={`file preview ${index}`}
                    /&amp;gt;
                  )}
                  &amp;lt;FileMetaData isImageFile={isImageFile}&amp;gt;
                    &amp;lt;span&amp;gt;{file.name}&amp;lt;/span&amp;gt;
                    &amp;lt;aside&amp;gt;
                      &amp;lt;span&amp;gt;{convertBytesToKB(file.size)} kb&amp;lt;/span&amp;gt;
                      &amp;lt;RemoveFileIcon
                        className="fas fa-trash-alt"
                        onClick={() =&amp;gt; removeFile(fileName)}
                      /&amp;gt;
                    &amp;lt;/aside&amp;gt;
                  &amp;lt;/FileMetaData&amp;gt;
                &amp;lt;/div&amp;gt;
              &amp;lt;/PreviewContainer&amp;gt;
            );
          })}
        &amp;lt;/PreviewList&amp;gt;
      &amp;lt;/FilePreviewContainer&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default FileUpload;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Use Case
&lt;/h4&gt;

&lt;p&gt;Let's now use the file upload component in App component to see it in action!&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;App.js&lt;/code&gt; file, we will create a simple form and add state to store the form data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from "react";

function App() {
  const [newUserInfo, setNewUserInfo] = useState({
    profileImages: []
  });

  const handleSubmit = (event) =&amp;gt; {
    event.preventDefault();
    //logic to create a new user...
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form onSubmit={handleSubmit}&amp;gt;
        &amp;lt;button type="submit"&amp;gt;Create New User&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to add the file upload component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from "react";
import FileUpload from "./components/file-upload/file-upload.component";

function App() {
  const [newUserInfo, setNewUserInfo] = useState({
    profileImages: []
  });

  const handleSubmit = (event) =&amp;gt; {
    event.preventDefault();
    //logic to create a new user...
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form onSubmit={handleSubmit}&amp;gt;
        &amp;lt;FileUpload
          accept=".jpg,.png,.jpeg"
          label="Profile Image(s)"
          multiple
        /&amp;gt;
        &amp;lt;button type="submit"&amp;gt;Create New User&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we have not added the &lt;code&gt;updateFilesCb&lt;/code&gt; prop yet. Before we can do that, we need to create a function that updates only the &lt;code&gt;profileImages&lt;/code&gt; property of the &lt;code&gt;newUserInfo&lt;/code&gt; state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const updateUploadedFiles = (files) =&amp;gt;
    setNewUserInfo({ ...newUserInfo, profileImages: files });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will now pass this function as the &lt;code&gt;updateFilesCb&lt;/code&gt; prop. So, any time the &lt;code&gt;files&lt;/code&gt; state changes in the file upload component, it will be saved in the &lt;code&gt;profileImages&lt;/code&gt; property of the &lt;code&gt;newUserInfo&lt;/code&gt; state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from "react";
import FileUpload from "./components/file-upload/file-upload.component";

function App() {
  const [newUserInfo, setNewUserInfo] = useState({
    profileImages: []
  });

  const updateUploadedFiles = (files) =&amp;gt;
    setNewUserInfo({ ...newUserInfo, profileImages: files });

  const handleSubmit = (event) =&amp;gt; {
    event.preventDefault();
    //logic to create new user...
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form onSubmit={handleSubmit}&amp;gt;
        &amp;lt;FileUpload
          accept=".jpg,.png,.jpeg"
          label="Profile Image(s)"
          multiple
          updateFilesCb={updateUploadedFiles}
        /&amp;gt;
        &amp;lt;button type="submit"&amp;gt;Create New User&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;“Why are we passing the &lt;code&gt;accept&lt;/code&gt; and &lt;code&gt;multiple&lt;/code&gt; prop to the file upload component?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since any additional props will get passed to the file input tag, the file input tag will have an &lt;code&gt;accept&lt;/code&gt; and &lt;code&gt;multiple&lt;/code&gt; attribute. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file" rel="noopener noreferrer"&gt;&lt;code&gt;multiple&lt;/code&gt;&lt;/a&gt; attribute allows a user to select multiple files in the file explorer. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file" rel="noopener noreferrer"&gt;&lt;code&gt;accept&lt;/code&gt;&lt;/a&gt; attribute prevents users from selecting file types different from the ones specified (i.e. jpg, png, jpeg in this case). &lt;/p&gt;

&lt;p&gt;Now that we are finished, run &lt;code&gt;npm start&lt;/code&gt; and visit localhost:3000. The following should appear:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1npgfxlibqdex9o2c8tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1npgfxlibqdex9o2c8tm.png" alt="Using the file upload component in a form" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For reference, the code can be found at&lt;br&gt;
&lt;a href="https://github.com/Chandra-Panta-Chhetri/react-file-upload-tutorial" rel="noopener noreferrer"&gt;https://github.com/Chandra-Panta-Chhetri/react-file-upload-tutorial&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Sending Emails Securely Using Node.js, Nodemailer, SMTP, Gmail, and OAuth2</title>
      <dc:creator>Chandra Panta Chhetri</dc:creator>
      <pubDate>Wed, 16 Dec 2020 23:05:54 +0000</pubDate>
      <link>https://dev.to/chandrapantachhetri/sending-emails-securely-using-node-js-nodemailer-smtp-gmail-and-oauth2-g3a</link>
      <guid>https://dev.to/chandrapantachhetri/sending-emails-securely-using-node-js-nodemailer-smtp-gmail-and-oauth2-g3a</guid>
      <description>&lt;p&gt;Many solutions online regarding configuring Nodemailer to use your Gmail requires you to &lt;strong&gt;enable less secure app access&lt;/strong&gt;. If that sounds too scary for you, then you have come to the right place! In this article, you will learn how to securely configure Nodemailer and Gmail.&lt;/p&gt;

&lt;p&gt;Let's start by understanding what Nodemailer is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nodemailer&lt;/strong&gt; is a module that makes &lt;strong&gt;sending emails from Node.js applications&lt;/strong&gt; ridiculously easy. &lt;/p&gt;

&lt;p&gt;The following are the main steps required to send emails:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating a transporter (object used to send emails) using 
either SMTP or some other transport mechanism&lt;/li&gt;
&lt;li&gt;Setting up message options (who sends what to whom)&lt;/li&gt;
&lt;li&gt;Sending the email by calling sendMail method on the 
transporter&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Less Secure Configuration
&lt;/h3&gt;

&lt;p&gt;Before we look at the secure solution for configuring Nodemailer and Gmail, let's look at the less secure solution. &lt;/p&gt;

&lt;p&gt;Using the steps above as a reference, here is the corresponding code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

//Step 1: Creating the transporter
const transporter = nodemailer.createTransport({
    service: "Gmail",
    auth: {
          user: "******@gmail.com",
          pass: "gmail_password"
        }
});

//Step 2: Setting up message options
const messageOptions = {
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: "put_email_of_sender"
};

//Step 3: Sending email
transporter.sendMail(messageOptions);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note: the solution above won't work until you &lt;strong&gt;enable less secure app access&lt;/strong&gt; in Google account settings. &lt;/p&gt;

&lt;p&gt;Now, let's look at the more secure solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Creating a Google Project
&lt;/h3&gt;

&lt;p&gt;Visit &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Developer Console&lt;/a&gt; to create a project. A project is needed so that we can create the necessary API credentials.&lt;/p&gt;

&lt;p&gt;Once in the console, click the dropdown in the top left corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3shpt049gyks0ackctl6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3shpt049gyks0ackctl6.jpg" alt="Project Dropdown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the create project window loads, click &lt;strong&gt;New Project&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9rimkadjgpm7yzry83zg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9rimkadjgpm7yzry83zg.jpg" alt="Project Window"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter in the project name and click &lt;strong&gt;create&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvyg31g1tb02qe1nnttd2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvyg31g1tb02qe1nnttd2.jpg" alt="3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Creating OAuth 2.0 API Credentials
&lt;/h3&gt;

&lt;p&gt;To get the client secret and client id, we need to create OAuth credentials. A client id identifies our app to Google's OAuth servers so that we can securely send emails from Nodemailer.&lt;/p&gt;

&lt;p&gt;Start by selecting &lt;strong&gt;credentials&lt;/strong&gt; in the sidebar on the left. Once selected, the following screen should appear: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4yahpghg15w6t3sertqa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4yahpghg15w6t3sertqa.jpg" alt="4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After clicking &lt;strong&gt;create credentials&lt;/strong&gt;, a dropdown will appear. In the dropdown, select &lt;strong&gt;OAuth client ID&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fr6xph65rhetlgeq4v8p7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fr6xph65rhetlgeq4v8p7.jpg" alt="5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before proceeding, we need to configure the consent screen. The consent screen configuration is important when an application offers Google Sign In. Nevertheless, it must be completed so we can create a client id and secret.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;configure consent screen&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5qvv6h483suw6lmdqlww.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5qvv6h483suw6lmdqlww.jpg" alt="6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;external&lt;/strong&gt; for the User Type and then click &lt;strong&gt;create&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F140rlcbclfw5lr4ywxqu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F140rlcbclfw5lr4ywxqu.jpg" alt="7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the multi-step form appears, fill out the required fields for each step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flpti10zypz3mnzo2tddz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flpti10zypz3mnzo2tddz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once on the last step, click &lt;strong&gt;back to dashboard&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhr0oek8sxlzkf9kpxg62.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhr0oek8sxlzkf9kpxg62.jpg" alt="8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to the Create OAuth client ID screen (page with the configure consent screen button). If the consent screen has been configured successfully, an application type dropdown should appear. Select &lt;strong&gt;Web application&lt;/strong&gt; and fill in the required field(s).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6updx36lrwlsscdmer7f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6updx36lrwlsscdmer7f.jpg" alt="9"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Authorized redirect URIs section, make sure to &lt;strong&gt;add &lt;a href="https://developers.google.com/oauthplayground" rel="noopener noreferrer"&gt;https://developers.google.com/oauthplayground&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now click &lt;strong&gt;create&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8ibsi4viip6xmto9yio6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8ibsi4viip6xmto9yio6.jpg" alt="9.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the client ID and client secret shown on the screen and save it for later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fi5bbno93r4vdul05sjo6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fi5bbno93r4vdul05sjo6.jpg" alt="9.2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: OAuth 2.0 Playground
&lt;/h3&gt;

&lt;p&gt;We also need a refresh token and access token which can be generated from the client id and secret. &lt;/p&gt;

&lt;p&gt;Start by visiting &lt;a href="https://developers.google.com/oauthplayground" rel="noopener noreferrer"&gt;https://developers.google.com/oauthplayground&lt;/a&gt;.&lt;br&gt;
Once on the page, click the gear icon and check the &lt;strong&gt;Use your own OAuth credentials&lt;/strong&gt; box. Then paste in the client id and secret from before.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9ort9i5akmrcaqggfppf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9ort9i5akmrcaqggfppf.jpg" alt="9.3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the left, under the Select &amp;amp; authorize APIs section, find Gmail API v1 and select &lt;strong&gt;&lt;a href="https://mail.google.com/" rel="noopener noreferrer"&gt;https://mail.google.com/&lt;/a&gt;&lt;/strong&gt;. Alternately, you can also &lt;strong&gt;type &lt;a href="https://mail.google.com/" rel="noopener noreferrer"&gt;https://mail.google.com/&lt;/a&gt;&lt;/strong&gt; into the &lt;strong&gt;Input your own scopes&lt;/strong&gt; field.&lt;/p&gt;

&lt;p&gt;Now click &lt;strong&gt;Authorize APIs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpetrnbxfghisnaoya0uv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpetrnbxfghisnaoya0uv.jpg" alt="9.4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the following pages appear, click allow so that Google OAuth 2.0 Playground has access to your Google account. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fissqbjh6b6cdn6n66ccl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fissqbjh6b6cdn6n66ccl.jpg" alt="9.41"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After being redirected back to the OAuth 2.0 Playground,&lt;br&gt;
click the &lt;strong&gt;Exchange authorization code for tokens&lt;/strong&gt; button under the &lt;strong&gt;Exchange authorization code for tokens&lt;/strong&gt; section.&lt;/p&gt;

&lt;p&gt;Once the refresh and access token is generated, copy the refresh token and save it for later. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4mmdp7040vchu6ozm32b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4mmdp7040vchu6ozm32b.jpg" alt="9.5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Writing Code
&lt;/h3&gt;

&lt;p&gt;Now that we have the client id, client secret, and refresh token, we can now use them to send emails!&lt;/p&gt;

&lt;p&gt;Start by making a new folder for the application and cd into the folder. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

mkdir sendEmails
cd sendEmails


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To initialize the app as a node project, run &lt;code&gt;npm init&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, let's install the npm packages.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

//Note: dotenv is a dev dependency
npm i nodemailer googleapis &amp;amp;&amp;amp; npm i dotenv --save-dev


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;googleapis&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;library for using Google APIs&lt;/li&gt;
&lt;li&gt;Will be used to dynamically generate access token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;dotenv&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;library for using environment variables&lt;/li&gt;
&lt;li&gt;Will be used to avoid having API keys in our code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like with any NPM packages, we start by requiring the packages. So, create an &lt;code&gt;index.js&lt;/code&gt; file and add the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Environment Variables Setup
&lt;/h4&gt;

&lt;p&gt;Typically when using sensitive info in code (e.g. API keys), the best practice is to use environment variables.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the root directory of the project and add the following:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

EMAIL=YOUR_GOOGLE_EMAIL_HERE
REFRESH_TOKEN=PASTE_REFRESH_TOKEN_HERE
CLIENT_SECRET=PASTE_CLIENT_SECRET_HERE
CLIENT_ID=PASTE_CLIENT_ID_HERE


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, we need to require and call the config method before requiring all the packages:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

require('dotenv').config();
const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;process.env&lt;/code&gt; now has the keys and values defined in the &lt;code&gt;.env&lt;/code&gt; file. For example, we can access client id via &lt;code&gt;process.env.CLIENT_ID&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating a transporter
&lt;/h4&gt;

&lt;p&gt;We first need to create an OAuth client with all of our info from before (client ID, client secret, and the OAuth Playground URL). The OAuth client will allow us to dynamically create an access token from a refresh token.&lt;/p&gt;

&lt;p&gt;“But wait, why can't we just use the access token from the OAuth Playground? Or why are we creating the access token dynamically?”&lt;/p&gt;

&lt;p&gt;Well, if you noticed earlier, there was a message indicating the access token would expire after 3582 seconds.&lt;/p&gt;

&lt;p&gt;The following code creates the OAuth client and provides it with the refresh token:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
);

oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Since getting the access token through the OAuth client is an asynchronous process, we need to wrap the above in an async function.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const createTransporter = async () =&amp;gt; {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });
};


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, we can get the access token by calling the getAccessToken method.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const accessToken = await new Promise((resolve, reject) =&amp;gt; {
  oauth2Client.getAccessToken((err, token) =&amp;gt; {
    if (err) {
      reject("Failed to create access token :(");
    }
    resolve(token);
  });
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You might be wondering, why are we wrapping the getAccessToken method call in a promise? This is because getAccessToken requires a callback and does not support using async await. Thus, we can either wrap it in a promise or create the transporter inside the callback. I prefer the former as it is more readable.&lt;/p&gt;

&lt;p&gt;Now for the main part, creating the transporter object itself.  To create it, we pass some configurations to the createTransport method. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    type: "OAuth2",
    user: process.env.EMAIL,
    accessToken,
    clientId: process.env.CLIENT_ID,
    clientSecret: process.env.CLIENT_SECRET,
    refreshToken: process.env.REFRESH_TOKEN
  }
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note: If you receive an "unauthorized client", try adding the following to the JS object above.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

tls: {
  rejectUnauthorized: false
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After the transporter is created, the completed createTransporter function should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const createTransporter = async () =&amp;gt; {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });

  const accessToken = await new Promise((resolve, reject) =&amp;gt; {
    oauth2Client.getAccessToken((err, token) =&amp;gt; {
      if (err) {
        reject();
      }
      resolve(token);
    });
  });

  const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      type: "OAuth2",
      user: process.env.EMAIL,
      accessToken,
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      refreshToken: process.env.REFRESH_TOKEN
    }
  });

  return transporter;
};


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notice we are returning the transporter instead of writing the code to send an email. We will create another function for sending the email for the sake of code readability and separations of concerns. &lt;/p&gt;

&lt;p&gt;Let's now create the sendEmail function. This function calls the createTransporter function and then the sendMail method that exists on the transporter.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

//emailOptions - who sends what to whom
const sendEmail = async (emailOptions) =&amp;gt; {
  let emailTransporter = await createTransporter();
  await emailTransporter.sendMail(emailOptions);
};


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;All that is left now is to send the email by calling the sendEmail function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sendEmail({
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: process.env.EMAIL
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The complete list of the email options can be found at &lt;a href="https://nodemailer.com/message/" rel="noopener noreferrer"&gt;https://nodemailer.com/message/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;node index.js&lt;/code&gt; from the terminal/command line and Voila! Here is the email we sent from the application!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd2gx8j0s6l0v4a3lonp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd2gx8j0s6l0v4a3lonp3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For reference, here is the completed &lt;code&gt;index.js&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

require("dotenv").config();
const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;

const createTransporter = async () =&amp;gt; {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });

  const accessToken = await new Promise((resolve, reject) =&amp;gt; {
    oauth2Client.getAccessToken((err, token) =&amp;gt; {
      if (err) {
        reject("Failed to create access token :(");
      }
      resolve(token);
    });
  });

  const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      type: "OAuth2",
      user: process.env.EMAIL,
      accessToken,
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      refreshToken: process.env.REFRESH_TOKEN
    }
  });

  return transporter;
};

const sendEmail = async (emailOptions) =&amp;gt; {
  let emailTransporter = await createTransporter();
  await emailTransporter.sendMail(emailOptions);
};

sendEmail({
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: process.env.EMAIL
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>node</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Firebase Security Rules</title>
      <dc:creator>Chandra Panta Chhetri</dc:creator>
      <pubDate>Sun, 06 Dec 2020 01:52:45 +0000</pubDate>
      <link>https://dev.to/chandrapantachhetri/firebase-security-rules-43kn</link>
      <guid>https://dev.to/chandrapantachhetri/firebase-security-rules-43kn</guid>
      <description>&lt;h4&gt;
  
  
  What is Firebase?
&lt;/h4&gt;

&lt;p&gt;Firebase is a platform that provides easy integration of commonly used services for mobile and web applications. These services include&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firestore&lt;/li&gt;
&lt;li&gt;Cloud Functions&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Cloud Storage&lt;/li&gt;
&lt;li&gt;Realtime Database&lt;/li&gt;
&lt;li&gt;Hosting&lt;/li&gt;
&lt;li&gt;Firebase ML&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How do security rules come into play?
&lt;/h3&gt;

&lt;p&gt;Traditionally, access to a database is controlled via server-side code. However, Firebase services such as Firestore allow client-side code to access the database directly. This poses a security risk as client-side code can be viewed by anyone. This is where Firebase's concept of security rules come into play. &lt;strong&gt;Firebase security rules control access to specific resources&lt;/strong&gt;. In other words, we can avoid the hassle of writing, maintaining, and deploying server-side code that controls access to resources. Security &lt;strong&gt;rules&lt;/strong&gt; can &lt;strong&gt;only&lt;/strong&gt; be &lt;strong&gt;written for Realtime Database, Firestore, and Cloud Storage&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Security Rules
&lt;/h3&gt;

&lt;p&gt;Security rules act as a middleware for when a service is used (e.g. reading a document in Firestore). When a request to access a service is denied due to security rules, the entire request fails. Rules can be written via Firebase console or an IDE that is using the Firebase CLI. Writing rules in the console provides the benefit of using Rules Playground before deploying the rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firestore Security Rules
&lt;/h3&gt;

&lt;p&gt;Firestore Security Rules consist of &lt;strong&gt;match statements&lt;/strong&gt; and &lt;strong&gt;allow expressions&lt;/strong&gt;. Match statements identify documents in your database and allow expressions control access to those documents.&lt;/p&gt;

&lt;p&gt;The basic structure of a security rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /&amp;lt;some_path&amp;gt;/ {
      allow read: if &amp;lt;some_condition&amp;gt;;
      allow write: if &amp;lt;some_condition&amp;gt;;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we are matching some path in the database and allowing read and write permissions based on a condition. In situations where we need finer access control, we can split read and write into more granular operations. &lt;/p&gt;

&lt;p&gt;read can be broken into &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get&lt;/code&gt; (applies to &lt;strong&gt;single document read requests&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list&lt;/code&gt; (applies to &lt;strong&gt;queries and collection read requests&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;write can be broken into&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also define custom functions to improve readability and reusability.&lt;/p&gt;

&lt;p&gt;Let's take a look at an example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';  
service cloud.firestore {
  match /databases/{database}/documents {  
    function isAdmin() {
      let userDoc = get(/databases/$(database)/documents/users/$(request.auth.uid));
      return userDoc == null ? false : userDoc.data.isAdmin;
    }
    function isAuthenticated(){
        return request.auth != null;
    }
    match /users/{userId}{
        allow read, delete: if isAuthenticated() 
                            &amp;amp;&amp;amp; request.auth.uid == resource.id;
        allow create: if request.resource.data.keys().hasAll(['name', 'email', 'isAdmin']) 
                      &amp;amp;&amp;amp; request.resource.data.keys().hasOnly(['name', 'email', 'isAdmin'])
                      &amp;amp;&amp;amp; request.resource.data.isAdmin == false;
        allow update: if isAuthenticated()
                      &amp;amp;&amp;amp; request.auth.uid == resource.id
                      &amp;amp;&amp;amp; request.resource.data.isAdmin == resource.data.isAdmin;
    }
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the example above, we have written the following rules for the users collection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading user(s) and deleting a user is allowed if the user is authenticated &amp;amp; the user document being read/deleted belongs to the authenticated user&lt;/li&gt;
&lt;li&gt;creating a user is allowed if the request body is of the form &lt;code&gt;{name, email, isAdmin}&lt;/code&gt; &amp;amp;&amp;amp; the isAdmin field is false (to prevent creating admin accounts from client-side)&lt;/li&gt;
&lt;li&gt;updating a user is allowed if the user is authenticated, the user document being updated belongs to the authenticated user, and the isAdmin field remains unchanged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might be wondering where variables and methods such as &lt;code&gt;get()&lt;/code&gt;, &lt;code&gt;request&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt; are coming from. Well, any custom functions or match statements in the &lt;code&gt;service cloud.firestore&lt;/code&gt; namespace has access to the following:&lt;/p&gt;

&lt;p&gt;Properties&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;request&lt;/code&gt; (request context)

&lt;ul&gt;
&lt;li&gt;Contains auth and incoming request information via auth &amp;amp; resource property&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;request.resource&lt;/code&gt; is used to enforce write operations (create, update, delete) have specific structure and field validations (e.g. age &amp;gt;= 15)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;request.auth&lt;/code&gt; is used to provide access based on if the request is authenticated&lt;/li&gt;
&lt;li&gt;More info can be found at &lt;a href="https://firebase.google.com/docs/reference/rules/rules.firestore.Request"&gt;https://firebase.google.com/docs/reference/rules/rules.firestore.Request&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resource&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;resource being written to or read (i.e. document in a collection)&lt;/li&gt;
&lt;li&gt;Document data and id can be accessed via &lt;code&gt;resource.data&lt;/code&gt; &amp;amp; &lt;code&gt;resource.id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Useful for enforcing certain fields cannot be changed. In the example above, we are preventing the &lt;code&gt;isAdmin&lt;/code&gt; field from changing in update user requests by &lt;code&gt;request.resource.data.isAdmin == resource.data.isAdmin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;More info can be found at &lt;a href="https://firebase.google.com/docs/reference/rules/rules.firestore.Resource"&gt;https://firebase.google.com/docs/reference/rules/rules.firestore.Resource&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Methods&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get(path)&lt;/code&gt; - &lt;strong&gt;path must be absolute path to a document&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Gets the contents of a document&lt;/li&gt;
&lt;li&gt;Useful when the modification of a document depends on another document&lt;/li&gt;
&lt;li&gt;e.g. The isAdmin() function in the example above gets the user document based on &lt;code&gt;request.auth.uid&lt;/code&gt; then accesses the &lt;code&gt;isAdmin&lt;/code&gt; field to check if the user is an Admin&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete list of methods and properties provided in the &lt;code&gt;service cloud.firestore&lt;/code&gt; namespace can be found at &lt;a href="https://firebase.google.com/docs/reference/rules/rules.firestore"&gt;https://firebase.google.com/docs/reference/rules/rules.firestore&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firebase also provides functions for working with different property types (e.g. list, boolean, map...etc) when writing rules. In the example above, we are using &lt;code&gt;hasOnly()&lt;/code&gt; and &lt;code&gt;hasAll()&lt;/code&gt; to enforce a user document only has 'name', 'email', and 'isAdmin' field.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More info can be found at &lt;a href="https://firebase.google.com/docs/reference/rules"&gt;https://firebase.google.com/docs/reference/rules&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cloud Storage Security Rules
&lt;/h3&gt;

&lt;p&gt;Security rules for Cloud Storage are similar to Firestore rules except instead of matching documents in a collection, we match directories or paths to files.&lt;/p&gt;

&lt;p&gt;Let's take a look at an example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /product_images/{imageName}
    {
        allow read, delete: if request.auth != null;
        allow create: if request.auth != null 
                      &amp;amp;&amp;amp; request.resource.contentType.matches('image/.*')
                      &amp;amp;&amp;amp; request.resource.size &amp;lt; 100000;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the similarities between Firestore rules and Cloud Storage rules. The main difference being &lt;code&gt;service firebase.storage&lt;/code&gt; namespace provides us with different properties on the &lt;code&gt;request&lt;/code&gt; variable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full list of the properties can be found at &lt;a href="https://firebase.google.com/docs/storage/security/rules-conditions"&gt;https://firebase.google.com/docs/storage/security/rules-conditions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on the example above, we have written the following rules for the product_images directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading and deleting files is allowed if the user is authenticated&lt;/li&gt;
&lt;li&gt;files can be added if the user is authenticated &amp;amp; the file is an image file that is smaller than 100 000 bytes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Realtime Database Security Rules
&lt;/h3&gt;

&lt;p&gt;Firebase's Realtime Database stores data as a large JSON object. This means when writing security rules, we match keys in the object instead of documents in a collection (like in Firestore).&lt;/p&gt;

&lt;p&gt;The basic structure of a security rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": &amp;lt;condition&amp;gt;,
        ".write": &amp;lt;condition&amp;gt;,
        ".validate": &amp;lt;condition&amp;gt;,
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before writing rules, an important concept to keep in mind is that &lt;strong&gt;read &amp;amp; write rules cascade&lt;/strong&gt;. In other words, if a rule grants read &amp;amp; write access at a particular path, then it grants access to all child nodes. Furthermore, &lt;strong&gt;shallower rules override deeper rules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's take a look at an example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "true",
        "bar": {
          /* bar can be read so this rule is ignored since read 
             was allowed already */
          ".read": false
        }
     }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firebase Realtime Database provides pre-defined variables similar to how &lt;code&gt;service firebase.storage&lt;/code&gt; namespace provides global variables &amp;amp; methods.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The complete list of variables and methods can be found at &lt;a href="https://firebase.google.com/docs/rules/rules-language#building_conditions"&gt;https://firebase.google.com/docs/rules/rules-language#building_conditions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A more practical example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth != null &amp;amp;&amp;amp; auth.uid == $uid",
        ".write": "auth != null &amp;amp;&amp;amp; auth.uid == $uid",
        ".validate": "newData.hasChildren(['name', 'age'])",
        "age": {
            ".validate": "newData.isNumber() &amp;amp;&amp;amp;
                          newData.val() &amp;gt; 0"
         }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the example above, we have written the following rules for the users node:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;write &amp;amp; read access is allowed user node belongs to the authenticated user&lt;/li&gt;
&lt;li&gt;a child node in the users node must have a 'name' &amp;amp; 'age' property&lt;/li&gt;
&lt;li&gt;the 'age' property must be a number greater than 0&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Although different services (Firestore, Cloud Storage, Realtime Database) have slightly different syntax when writing rules, at a high level we are doing the same thing. More specifically, we match a path to a resource and control access to that resource via read and write conditions. &lt;/p&gt;

&lt;p&gt;Nevertheless,  Firebase security rules serve the purpose of protecting data from malicious users. Thus, I hope this article helped you understand the main concepts when writing security rules! Now go protect your data!&lt;/p&gt;

</description>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
