Skip to content

gauravadhikari1997/react-social-media

Repository files navigation

react-social-media

Social Media App using ReactJs

Creating React app from scratch

Takeaways

mkdir my-react-app
cd my-react-app
npm init -y
npm install --save react react-dom

index.html Main.js

//package are on our computer, so to bundle it to deliver to users
//and to handle JSX

npm install --save webpack webpack-cli webpack-dev-server

npm install --save @babel/core @babel/preset-env @babel/preset-react babel-loader

//webpack configuration file
webpack.config.js

const path = require("path");

module.exports = {
  entry: "./app/Main.js",
  output: {
    publicPath: "/",
    path: path.resolve(__dirname, "app"),
    filename: "bundled.js",
  },
  mode: "development",
  devtool: "source-map",
  devServer: {
    port: 3000,
    contentBase: path.join(__dirname, "app"),
    hot: true,
    historyApiFallback: { index: "index.html" },
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-react",
              ["@babel/preset-env", { targets: { node: "12" } }],
            ],
          },
        },
      },
    ],
  },
};

//now include script tag to include bundled.js file

index.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8" />
    <title></title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundled.js"></script>
  </body>
</html>

created components

//for routing

npm install --save react-router-dom
import {BrowserRouter, Switch, Route, Link } from 'react-route-dom';

function Main() {
  return (
<>
    <BrowserRouter>
      <Header />
      <Switch>
        <Route path="/" exact>
          <HomeGuest />
        </Route>
        <Route path="/about-us">
          <About />
        </Route>
        <Route path="/terms">
          <Terms />
        </Route>
      </Switch>
      <Footer />
    </BrowserRouter>

	<footer>
      <p>
        <Link to="/" className="mx-1">
          Home
        </Link>{" "}
        |
        <Link className="mx-1" to="/about-us">
          About Us
        </Link>{" "}
        |
        <Link className="mx-1" to="/terms">
          Terms
        </Link>
      </p>
</footer>
</>
  );
}

//using mongodb for database
//sign up at mondodb.com and click on Connections
//Type database name and collection name
//now click on connect>add different IP address
//0.0.0.0/0 for all users IP
//set admin username and password
//Choose a connection method>connect you app
//Driver=Node and copy string
//register using axios request to mongodb

async function handleSubmit(e) {
    e.preventDefault();
    try {
      await Axios.post("http://localhost:8080/register", {
        username,
        email,
        password,
      });
      console.log("Successfully registered!");
    } catch (e) {
      console.log(e);
    }
  }

//sign in

async function handleSubmit(e) {
    e.preventDefault();
    try {
      const response = await Axios.post("http://localhost:8080/login", {
        username,
        password,
      });
      if (response.data) {
        localStorage.setItem("rsmToken", response.data.token);
        localStorage.setItem("rsmUsername", response.data.username);
        localStorage.setItem("rsmAvatar", response.data.avatar);
        props.setIsLoggedIn(true);
      } else {
        console.log("Incorrect username/password");
      }
    } catch (e) {
      console.log("Error occured", e);
    }
  }

//set up routing, to redirect to new post address

	<Route path="/post/:id">
          <ViewSinglePost />
        </Route>

//then in createPost function

import {withRouter} from 'react-router-dom'

props.history.push(`/post/${response.data}`)

//using react context API
//for making state globally available

create ExampleContext.js
import {createContext} from 'react';
const ExampleContext = createContext();
export default ExampleContext;

//import this context in main file
//wrap everything inside it
//pass the states in value

<ExampleContext.Provider value={{setIsLoggedIn, addFlashMessage}} >
...
</ExampleContext.Provider>

//using Context in component, CreatePost

import {useContext} from 'react';
import ExampleContext from '../ExampleContext';

function CreatePost() {
const {addFlashMessage} = useContext(ExampleContext);

//now call it directly as {addFlashMessage} to use


//now to make it further more easier and cleaner
//for state to be used in different components
//we will levarage useReducer from react
// it's mixture(or duo) useContext+useReducer

import {useReducer} from react;

import StateContext from "./StateContext";
import DispatchContext from "./DispatchContext";
//we have made two context files to avoid
//unnecessary reloading of components where 
//state change isn't happening

function main(){
const initialState = {
isLoggedIn: Boolean(localStorage.getItem('key'))
flashMessages: []
}

function ourReducer(state, action){
    switch (action.type) {
      case "login":
        return {
          isLoggedIn: true,
          flashMessages: state.flashMessages,
        };
      case "logout":
        return {
          isLoggedIn: false,
          flashMessages: state.flashMessages,
        };
      case "flashMessages":
        return {
          isLoggedIn: state.isLoggedIn,
          flashMessages: state.flashMessages.concat(action.value),
        };
    }
}
const [state, dispatch] = useReducer(ourReducer, initialState);

//wrap whole return in

<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
...
</StateContext.Provider>
</DispatchContext.Provider>

//in Header we only need state change
//so we'll import StateContext and use state

//in HeaderLoggedIn and HeaderLoggedOut and CreatePost
//we need to dispatch actions like login, logout and
//flashmessage so we'll use DispatchContext there like

import {useContext} from 'react';

import DispatchContext from '../DispatchContext';

function HeaderLoggedIn() {
const appDispatch = useContext(DispatchContext);
function handleSignOut() {
    appDispatch({ type: "logout" });
    localStorage.removeItem("rsmToken");
    localStorage.removeItem("rsmUsername");
    localStorage.removeItem("rsmAvatar");
  }

//because we cannot mutate state in react
//we will use immer to levarage immutability

npm install immer use-immer
import {useImmerReducer} from 'use-immer';

//just rename useReducer to useImmerReducer
//and in ourReducer function

function ourReducer(draft, action){
    switch (action.type) {
      case "login":
	draft.isLoggedIn = true
        return;
      case "logout":
	draft.isLoggedIn = false
        return;
      case "flashMessages":
	draft.flashMessages.push(action.value)
        return;
    }
}

//to use some dynamic value from URL
//we levarage useParams from react-router-dom

import {useParams} from 'react-router-dom';

const {username} = useParams();

//we sent this dynnamic var in Route

<Route path='/profile/:username'>
	<Profile />
</Route>

//when we interact with a component that makes
//some async request and the user goes away
//from that component then memoey leak warning
//will be issue and to fix this
//we will cancel await request in the useEffect

useEffect(() => {
    const ourRequest = Axios.CancelToken.source();
    try {
      async function fetchData() {
        const response = await Axios.get(`/profile/${username}/posts`, {
          CancelToken: ourRequest.token,
        });
        setPosts(response.data);
        setIsLoading(false);
      }
      fetchData();
      return () => {
        ourRequest.cancel();
      };
    } catch (e) {
      console.log("There was some error", e);
    }
  }, []);

//react lazy loading is used to exempt some components
//which're either large in size or take take more time
//to save up resources

import { Suspense } from React;

const Chat = React.lazy(() => import('./components/Chat'));

<Suspense fallback="">
	<Chat />
</Suspense>

//for production purposes
//building dist copy of our project

webpack.config.js

const currentTask = process.env.npm_lifecycle_event;
const path = require("path");
const Dotenv = require("dotenv-webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackHarddiskPlugin = require("html-webpack-harddisk-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const fse = require("fs-extra");

/*
  Because I didn't bother making CSS part of our
  webpack workflow for this project I'm just
  manually copying our CSS file to the DIST folder.
*/

class RunAfterCompile {
  apply(compiler) {
    compiler.hooks.done.tap("Copy files", function () {
      fse.copySync("./app/main.css", "./dist/main.css");

      /*
        If you needed to copy another file or folder
        such as your "images" folder, you could just
        call fse.copySync() as many times as you need
        to here to cover all of your files/folders.
      */
    });
  }
}

config = {
  entry: "./app/Main.js",
  output: {
    publicPath: "/",
    path: path.resolve(__dirname, "app"),
    filename: "bundled.js",
  },
  plugins: [
    new Dotenv(),
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: "app/index-template.html",
      alwaysWriteToDisk: true,
    }),
    new HtmlWebpackHarddiskPlugin(),
  ],
  mode: "development",
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-react",
              ["@babel/preset-env", { targets: { node: "12" } }],
            ],
          },
        },
      },
    ],
  },
};

if (currentTask == "webpackDev" || currentTask == "dev") {
  config.devtool = "source-map";
  config.devServer = {
    port: 3000,
    contentBase: path.join(__dirname, "app"),
    hot: true,
    historyApiFallback: { index: "index.html" },
  };
}

if (currentTask == "webpackBuild") {
  config.plugins.push(new CleanWebpackPlugin(), new RunAfterCompile());
  config.mode = "production";
  config.output = {
    publicPath: "/",
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[chunkhash].js",
    chunkFilename: "[name].[chunkhash].js",
  };
}

module.exports = config;

//npm install above required packages

.env
BACKENDURL = http://localhost:8080

change index to index-template

previewDist.js

const express = require("express");
const path = require("path");
const app = new express();
app.use(express.static(path.join(__dirname, "dist")));
app.get("*", (req, res) => res.sendFile(__dirname + "/dist/index.html"));
app.listen("4000");

package.json

"scripts": {
    "dev": "webpack-dev-server",
    "webpackBuild": "webpack",
    "previewDist": "node previewDist"
}

//for slow connection, we are building static html
//to show up as skeleton till bundle.js loads

generateHtml.js

import React from "react";
import ReactDOMServer from "react-dom/server";
import fs from "fs";
import Footer from "./app/components/Footer";
import Header from "./app/components/Header";
import LoadingIcon from "./app/components/LoadingIcon";
import { StaticRouter as Router } from "react-router-dom";
import StateContext from "./app/StateContext";

function Shell() {
  return (
    <StateContext.Provider value={{ loggedIn: false }}>
      <Router>
        <Header staticEmpty={true} />
        <div className="py-5 my-5 text-center">
          <LoadingIcon />
        </div>
        <Footer />
      </Router>
    </StateContext.Provider>
  );
}

function html(x) {
  return `<!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <title>OurApp</title>
      <link href="https://fonts.googleapis.com/css?family=Public+Sans:300,400,400i,700,700i&display=swap" rel="stylesheet" />
      <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
      <script defer src="https://use.fontawesome.com/releases/v5.5.0/js/all.js" integrity="sha384-GqVMZRt5Gn7tB9D9q7ONtcp4gtHIUEW/yG7h98J7IpE3kpi+srfFyyB/04OV6pG0" crossorigin="anonymous"></script>

      <link rel="stylesheet" href="/main.css" />
    </head>
    <body>
      <div id="root">
      ${x}
      </div>
    </body>
  </html>
  `;
}

/*
  We can use ReactDomServer (you can see how we imported
  that at the very top of this file) to generate a string
  of HTML text. We simply give it a React component and
  here we are using the JSX syntax.
*/
const reactHtml = ReactDOMServer.renderToString(<Shell />);

/*
  Call our "html" function which has the skeleton or
  boilerplate HTML, and give it the string that React
  generated for us. Our "html" function will insert
  the React string inside the #app div. So now we will
  have a variable in memory with the exact string we
  want, we just need to save it to a file.
*/
const overallHtmlString = html(reactHtml);

/*
  This course is not about Node, but here we are simply
  saving our generated string into a file named
  index-template.html. Please note that this Node task
  will fail if the directory we told it to live within
  ("app" in this case) does not already exist.
*/
const fileName = "./app/index-template.html";
const stream = fs.createWriteStream(fileName);
stream.once("open", () => {
  stream.end(overallHtmlString);
});

package.json

"scripts": {
    "dev": "npm-run-all -s generate webpackDev",
    "webpackDev": "webpack-dev-server",
    "build": "npm-run-all -s generate webpackBuild",
    "webpackBuild": "webpack",
    "generate": "babel-node --presets=@babel/preset-react,@babel/preset-env generateHtml.js",
    "previewDist": "node previewDist",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

//here npm-run-all is node package, -s to sequentially run scripts
//npm run webpackDev for development
//npm run webpackBuild for production -> npm run previewDist to view