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;