THAPA TECHNICAL

HOUSE OF WEB DEVELOPERS AND TECHNOLOGY.

Tech News Website using React JS

 Tech News Website using React JS







App.js File

import Pagination from "./Pagination";
import Search from "./Search";
import Stories from "./Stories";
import "./App.css";

const App = () => {
  return (
    <>
      <Search />
      <Pagination />
      <Stories />
    </>
  );
};

export default App;



Context.js File 


// context  creation ✅
// provider ✅
// consumer lenghty remove useContext hook
// useContext hook ✅

import React, { useContext, useReducer, useEffect } from "react";
import reducer from "./reducer";

let API = "https://hn.algolia.com/api/v1/search?";

const initialState = {
  isLoading: true,
  query: "CSS",
  nbPages: 0,
  page: 0,
  hits: [],
};

const AppContext = React.createContext();

// to create a provider fucntion
const AppProvider = ({ children }) => {
  // const [state, setstate] = useState(initialState);

  const [state, dispatch] = useReducer(reducer, initialState);

  const fecthApiData = async (url) => {
    dispatch({ type: "SET_LOADING" });

    try {
      const res = await fetch(url);
      const data = await res.json();
      console.log(data);
      dispatch({
        type: "GET_STORIES",
        payload: {
          hits: data.hits,
          nbPages: data.nbPages,
        },
      });
      // isLoading = false;
    } catch (error) {
      console.log(error);
    }
  };

  // to remove the post
  const removePost = (post_ID) => {
    dispatch({ type: "REMOVE_POST", payload: post_ID });
  };

  // plz subscribe thapa technical youtube channel

  // search
  const searchPost = (searchQuery) => {
    dispatch({
      type: "SEARCH_QUERY",
      payload: searchQuery,
    });
  };

  // pagination
  const getNextPage = () => {
    dispatch({
      type: "NEXT_PAGE",
    });
  };

  const getPrevPage = () => {
    dispatch({
      type: "PREV_PAGE",
    });
  };

  // to call teh api func
  useEffect(() => {
    fecthApiData(`${API}query=${state.query}&page=${state.page}`);
  }, [state.query, state.page]);

  return (
    <AppContext.Provider
      value={{ ...state, removePost, searchPost, getNextPage, getPrevPage }}>
      {children}
    </AppContext.Provider>
  );
};

// custom hook chitsreate
const useGlobalContext = () => {
  return useContext(AppContext);
};

export { AppContext, AppProvider, useGlobalContext };



Stories.js File

import React from "react";
import { useGlobalContext } from "./context";

const Stories = () => {
  const { hits, isLoading, removePost } = useGlobalContext();
  if (isLoading) {
    return (
      <>
        <h1>Loading.....</h1>
      </>
    );
  }
  return (
    <>
      <div className="stories-div">
        {hits.map((curPost) => {
          const { title, author, objectID, url, num_comments } = curPost;
          return (
            <div className="card" key={objectID}>
              <h2>{title}</h2>
              <p>
                By <span> {author}</span> | <span> {num_comments} </span>
                comments
              </p>
              <div className="card-button">
                <a href={url} target="_blank">
                  Read More
                </a>
                <a href="#" onClick={() => removePost(objectID)}>
                  Remove
                </a>
              </div>
            </div>
          );
        })}
      </div>
    </>
  );
};

export default Stories;



App.css File

@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:wght@400&family=Poppins:wght@400;600&display=swap");

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: "Poppins", sans-serif;
}

html {
  font-size: 62.5%;
}

body {
  background-color: rgb(219, 230, 253);
}

h1 {
  font-size: 3.2rem;
  margin: 2rem 0;
  text-align: center;
}

h2 {
  font-family: "IBM Plex Serif", serif;
  font-size: 2.4rem;
}

p {
  margin: 2rem 0;
  color: rgba(21, 19, 60, 0.5);
  font-weight: 400;
  font-size: 1.6rem;
  display: inline-block;
}

span {
  text-transform: capitalize;
  font-weight: bold;
}

.stories-div {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  gap: 4rem;
}

.card {
  min-width: 20rem;
  width: 40vw;
  padding: 4rem;
  background-color: #ffffff;
  box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
  border-radius: 0.5rem;
  color: #15133c;
}

.card-button {
  flex-direction: row;
  width: 100%;
  display: flex;
  justify-content: space-between;
}

.card-button a {
  color: #92b4ec;
  font-weight: 400;
  font-size: 1.6rem;
}

.card-button a:last-child {
  color: red;
}

form {
  display: grid;
  place-items: center;
}

// subscribe to Thapa Technical YouTube
// channel for more awesome videos 😀

input {
  padding: 1rem;
  min-width: 40rem;
  font-size: 2rem;
  text-transform: capitalize;
  border: none;
  border-bottom: 0.2rem solid #15133c;
  outline: none;
}

.pagination-btn {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

button {
  padding: 0.8rem 2.2rem;
  border: none;
  font-size: 1.6rem;
  background-color: #15133c;
  color: #ffffff;
  margin: 3rem;
  cursor: pointer;
  display: inline-block;
}

/* responsive layout  */

@media (max-width: 998px) {
  html {
    font-size: 56%;
  }

  .card {
    min-width: 45rem;
    padding: 2.5rem;
  }
}

@media (max-width: 768px) {
  html {
    font-size: 51%;
  }

  .card {
    min-width: 35rem;
    padding: 2rem;
  }
}



Search.js File


import React from "react";
import { useGlobalContext } from "./context";

const Search = () => {
  const { query, searchPost } = useGlobalContext();
  return (
    <>
      <h1>Thapa Technical Tech Website</h1>
      <form onSubmit={(e) => e.preventDefault()}>
        <div>
          <input
            type="text"
            placeholder="search here"
            value={query}
            onChange={(e) => searchPost(e.target.value)}
          />
        </div>
      </form>
    </>
  );
};

export default Search;



Pagination.js File


import React from "react";
import { useGlobalContext } from "./context";

const Pagination = () => {
  const { page, nbPages, getPrevPage, getNextPage } = useGlobalContext();
  return (
    <>
      <div className="pagination-btn">
        <button onClick={() => getPrevPage()}>PREV</button>
        <p>
          {page + 1} of {nbPages}
        </p>
        <button onClick={() => getNextPage()}>NEXT</button>
      </div>
    </>
  );
};

export default Pagination;




Reducer.js File


const reducer = (state, action) => {
  switch (action.type) {
    case "SET_LOADING":
      return {
        ...state,
        isLoading: true,
      };
    case "GET_STORIES":
      return {
        ...state,
        isLoading: false,
        hits: action.payload.hits,
        nbPages: action.payload.nbPages,
      };
    case "REMOVE_POST":
      return {
        ...state,
        hits: state.hits.filter(
          (curElem) => curElem.objectID !== action.payload
        ),
      };
    case "SEARCH_QUERY":
      return {
        ...state,
        query: action.payload,
      };

    case "NEXT_PAGE":
      let pageNumInc = state.page + 1;

      if (pageNumInc >= state.nbPages) {
        pageNumInc = 0;
      }
      return {
        ...state,
        page: pageNumInc,
      };

    case "PREV_PAGE":
      let pageNum = state.page - 1;

      if (pageNum <= 0) {
        pageNum = 0;
      }

      return {
        ...state,
        page: pageNum,
      };
   
    // you can add the default case too
  }

  return state;
};

export default reducer;