Creating a history page with React Hooks - reactjs

I am trying to create a history page with react hooks that keeps track of the users most recent searches they don't have to be persistent through refreshes only from this session.
my search component looks like this this is a simple app that does not need a UI just a simple navigation on the search page it will show the results and on the history page I would like to be able to show the previous searches from this session
I am trying to keep track of the debouncedTerm so I can display it in a new component
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Search = () => {
const history = [];
const [term, setTerm] = useState('');
const [debouncedTerm, setDebouncedTerm] = useState(term);
const [results, setResults] = useState([]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 1000);
return () => {
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const search = async () => {
const { data } = await axios.get('http://hn.algolia.com/api/v1/search?', {
params: {
query: debouncedTerm,
},
});
setResults(data.hits);
};
if (debouncedTerm) {
search();
}
}, [debouncedTerm]);
const renderedResults = results.map((result) => {
return (
<div key={result.objectID} className="item">
<div className="right floated content">
<a className="ui button" href={result.url}>
Go
</a>
</div>
<div className="content">
<div className="header">{result.title}</div>
</div>
</div>
);
});
return (
<div>
<div className="ui form">
<div className="field">
<label>Hacker News Search:</label>
<input
value={term}
onChange={(e) => setTerm(e.target.value)}
className="input"
/>
</div>
</div>
<div className="ui celled list">{renderedResults}</div>
</div>
);
};
export default Search;

Your code looks like it's going in the right direction but you have a constant const history = []; and you must keep in mind that this will not work, because you will have a new constant re-declared in every render. You must use React setState like so:
const [history, setHistory] = useState([]);
You can read more about it in the React documentation.
edit:
In order to add new elements to the existing history you have to append it like this:
setHistory(oldHistory => [...oldHistory, newHistoryElement]);

Related

How to change the Index of data coming from an API in Next js

import Axios from "axios";
import React, { useEffect, useState } from "react";
import styles from "../../styles/Questions.module.css";
const quiz = ({ question, index}) => {
console.log(question);
const [number, setNumber] = useState(index); // I passed index here as props from the getStaticProps function.
const [state, setState] = useState();
const [image, setImage] = useState();
const [numbering, setNumbering] = useState(1);
useEffect(() => {
if (localStorage) {
const getNameState = localStorage.getItem("name");
setState(getNameState);
}
}, []);
useEffect(() => {
if (localStorage) {
const getPhotoState = localStorage.getItem("photoUrl");
setImage(getPhotoState);
}
}, []);
console.log("I Am question", index, number);
return (
<div className={styles.container}>
<div className={styles.profile}>
<div className={styles.mainProfile}>
<div className={styles.profilePic}>
<img src={image} alt="img" />
</div>
<h2>{state} </h2>
</div>
</div>
<div className={styles.main}>
<h2>
{numbering}. {question.question}{" "}
</h2>
<div>
<ul className={styles.list1}>
<li>{question.incorrect_answers[0]} </li>
<li>{question.incorrect_answers[1]} </li>
</ul>
<ul className={styles.list2}>
<li>{question.incorrect_answers[2]} </li>
<li>{question.correct_answer} </li>
</ul>
</div>
<button>Previous </button>
<button onClick={() => setNumber(number + 1)}>Next </button> // Here is the click supposed to trigger the number(state) to change the index
</div>
{number}
</div>
);
};
export const getStaticProps = async () => {
const data = await Axios.get("https://opentdb.com/api.php?amount=10");
const indexing = 0; // where indexing is 0
return {
props: {
question: data.data.results[indexing], // Indexing here is supposed to be the changeable index.
index: indexing, // i set Indexing to be equal to index
},
};
};
export default quiz;
So I am new to Next Js and I am trying to change the index of the data coming from the API on a quiz app, I want one question displayed until a click, which is supposed the change the index, Problem is the index as a state is changing but the "indexing" is is not changing(Which is exactly what i need to change). So please how do i go about it??....Thanks in advance guys.

The url is changing but it is not redirecting me

What I want is when I click on:
let Afeef = `/${category}`
<Link to={Afeef} className='products-categories'> <h4>{category}</h4></Link>
It should change products according to URL which could be "/electronics","/jewelry" etc but the problem I am facing is that it is changing my URL but the products are not changing. I can't understand what is the problem here. I tried different things but I cant understand it.
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom';
import './Allproducts.css'
import Categories from './categories.json'
import ForYouItem from './ForYouItem'
export default function Allproducts(props) {
const [products, setProducts] = useState([]);
useEffect(() => {
fetch(`https://fakestoreapi.com/products/category/${props.category}`)
.then((res) => res.json())
.then((data) => setProducts(data))
}, [])
const [categories, setCategories] = useState([])
const updateCategory = async ()=> {
const url = "./categories.json"
let data = await fetch(url);
let parsedData = await data.json()
setCategories(parsedData.title)
}
useEffect(() => {
updateCategory();
}, [])
return (
<>
<div className="banner">
<h1>Afeef</h1>
<h4>Get best items in budget</h4>
</div>
<div className="main-grid">
<div className="left-grid">
<div className="left-categories">
<h1>Collections</h1>
{categories.map((category) => {
let Afeef = `/${category}`
return (
<Link to={Afeef} className='products-categories'> <h4>{category}</h4></Link>
)
}
)}
</div>
</div>
<div className="right-grid">
<div className="row ">
{products.map((product) => {
return (
<div className="col-md-4 my-2 Products-1">
<ForYouItem Title={product.title.slice(0, 50)} Price={product.price} Imageurl={product.image} rate={product.rating.rate} count={product.rating.count} />
</div>
)
}
)}
</div>
</div>
</div>
</>
)
}
im gonna try to explain what i understand from your code. So based on the flow of your code, the product can only be fetch once when the page loaded.
but i think in your useEffect that fetch product, you can add the state of Categories in the bracket "[categories]".
then add an onclick setstate product in your link.
so when you click your link, the categories state is updated. then because the bracket inside useEffect that have [categories] is updated the useEffect is run. hence fething new product.

Search bar not displaying results in React

I am fetching the data of all products from an API call. All these products are objects. I am then checking if the value of search sting is present in all the productsindividually. If yes, that product is added to a different array. All the elements'titles in this different array are displayed as a dropdown menu.
Where am I going wrong? Plz help
Code on React:
import Head from "next/head";
const App = () => {
var resultfoundarray=[];
const [Searchquery,setSearchquery] = useState("");
const [AllProducts,setAllProducts] = useState([]);
const allproducts = () =>{
fetch('https://fakestoreapi.com/products')
.then(res=>res.json())
.then(json=>{console.log(json);
setAllProducts(json);
console.log(AllProducts);
})
}
const search = () =>{
allproducts();
AllProducts.forEach(prod => {
if(Searchquery in prod){
resultfoundarray.push(prod.title);
}
});
}
return(
<>
<StrictMode>
<Head>
<link rel="stylesheet" href="./css/general.css"></link>
</Head>
<div>
<div className="searchbardiv">
<div>
<input type="text" placeholder="Search" onChange={e=>setSearchquery(e.target.value) ></input>
<span><button type="submit" onClick={ e => search()}>Search</button></span>
<div>
<select>
{resultfoundarray.map((prodtitle) => {
<option>
{prodtitle}
</option>
})}
</div>
</div>
</StrictMode>
</>
)
}
export default App;
The main problem is that the fetch is running in the background, updating AllProducts asynchronously. In your search function, you trigger the fetch and access AllProducts immediately after starting the fetch, before the results came back.
You should trigger the fetch when the component mounts:
useEffect(() => allProducts(), []);
And then react to state changes in AllProducts
useEffect(() => AllProducts?.forEach(prod => {
if (Searchquery in prod) {
resultfoundarray.push(prod.title);
}
}, [AllProducts]);
There are more minor issues (e.g. it would be better to use useState for managing resultfoundarray too), but I guess you'll figure that out.
The full code would look like this (just copied & pasted, there is no guarantee it works):
import React from "react";
const App = () => {
var resultfoundarray = [];
const [Searchquery, setSearchquery] = useState("");
const [AllProducts, setAllProducts] = useState([]);
const allproducts = () => {
fetch("https://fakestoreapi.com/products")
.then((res) => res.json())
.then((json) => {
console.log(json);
setAllProducts(json);
console.log(AllProducts);
});
};
// load all products when component mounts
useEffect(() => allproducts(), []);
// update results found after products are loaded
// or the when search query changes
useEffect(
() =>
AllProducts?.forEach((prod) => {
if (Searchquery in prod) {
resultfoundarray.push(prod.title);
}
}),
[AllProducts, SearchQuery]
);
return (
<>
<StrictMode>
<Head>
<link rel="stylesheet" href="./css/general.css"></link>
</Head>
<div className="searchbardiv">
<div>
<input
type="text"
placeholder="Search"
onChange={(e) => setSearchquery(e.target.value)}
></input>
<span>
<button type="submit" onClick={(e) => search()}>
Search
</button>
</span>
<div>
<select>
{resultfoundarray.map((prodtitle) => {
<option>{prodtitle}</option>;
})}{" "}
</select>
</div>
</div>
</div>
</StrictMode>
</>
);
};
export default App;
There are issues as others have pointed out. But the issue you are not seeing any result being displayed is because of this condition
if(Searchquery in prod){
You are searching by the product property name instead of the product title. To correct that, the condition should be
if (prod.title.includes(Searchquery ))

Pagination on React search bar

EDIT: I got something to work and updated my code. Unfortunately its only partialy working. What am I doing wrong?
Also, I would like to just display current and last page - any good suggestions to how I do that?
Furthermore, if someone can tell how to do a canceltoken, it would be a big help. Can seem to figure out how to put it there.
I am trying to make a search bar with pagination. I have been succesful in making it searchable with api, but the pagination I can't seem to wrap my head around.
Could someone please provide me an example of how I will be able to do it? I've been through tons of tutorials and blogs, youtube, github and so on already.
import {useEffect, useState} from 'react';
import './App.css';
import axios from 'axios';
function App() {
const [data, setData] = useState([] as any[]);
const [query, setQuery] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const handleClick = (event:any) => {
setCurrentPage(Number(event.target.id));
};
const pages = [];
for (let i=1; i<=Math.ceil(data.length/itemsPerPage);i++ ){
pages.push(i);
}
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = data.slice(indexOfFirstItem, indexOfLastItem);
const renderPageNumbers = pages.map(number => {
return (
<li key={number} onClick={handleClick}>
{number}
</li>
)
})
useEffect(() => {
const fetchData = async () => {
try {
const {data} = await axios.get(`https://2isz0zc3qk.execute-api.eu-central-1.amazonaws.com/staging/search?term=${query}`)
setData(data.items)
} catch (error) {
console.error(error)
}
}
fetchData()
}, [query]);
return (
<div className="App">
<div className="search">
<input type="text"
placeholder={"Search product..."}
className={"input"}
onChange={event => setQuery(event.target.value)}
value={query}
/>
</div>
<div className="results">
{currentItems.map((value, key) => (
<ul className='items'>
<li className='item'>
<a href={`https://www.whiteaway.com${value.url}`} target='_blank' rel='noreferrer'>
<img src={value.image} alt={value.name} />
<p>{value.name}</p>
</a>
</li>
</ul>
))}
</div>
<div>
<ul className="pageNumbers">
{renderPageNumbers}
</ul>
</div>
</div>
);
}
export default App;
You can take a look how it's done here using React Query:
https://react-query.tanstack.com/examples/pagination

Rerender sibling component in React

I am new to React. I am stuck on this problem for days now.
I have got a parent component which wraps two sibling components, "FileUpload" and "Documents"
The "FileUpload" is for uploading a file and "Documents" is for displaying all the uploaded files.
I want the "Documents" rerender after a new file is uploaded via "FileUpload", so that it shows the new file in the UI.
What would be the best approach to achieve this ?
Below is the code I have written so far for the sibling components:
FileUpload:
import React, { useState } from "react";
import Axios from "axios";
const FileUpload = (props) => {
const [files, setFiles] = useState([]);
const onInputChange = (e) => {
setFiles(e.target.files);
};
const handleSubmit = async (e) => {
e.preventDefault();
const data = new FormData();
for (let i = 0; i < files.length; i++) {
// console.log(files);
data.append("file", files[i]);
}
data.append("parentDbId", props.parentDbId);
data.append("parentObject", props.parentObject);
//console.log(data);
try {
await Axios.post("http://localhost:5000/upload", data);
} catch (err) {
console.error(err.message);
}
};
return (
<form
// action="http://localhost:5000/upload"
// method="POST"
//encType="multipart/form-data"
onSubmit={handleSubmit}
>
<div className="row mb-3">
<div className="col-lg-4">
<label htmlFor="formFileMultiple" className="form-label mb-0">
Add files
</label>
<input
className="form-control"
type="file"
id="formFileMultiple"
name="file"
multiple
onChange={onInputChange}
/>
</div>
<div className="col-lg-4 mt-0 gx-0">
<button type="submit" className="btn btn-primary mt-4">
Upload
</button>
</div>
</div>
</form>
);
};
export default FileUpload;
====================================================================
Documents:
import React, { useState, useEffect } from "react";
import axios from "axios";
const Documents = (props) => {
const parentDbId = props.parentDbId;
const [documents, setDocuments] = useState([]);
//mount
useEffect(() => {
console.log("first use effect");
loadDocuments();
}, []);
const loadDocuments = async () => {
const result = await axios.get(
`http://localhost:5000/documents/${parentDbId}`
);
setDocuments(result.data);
};
return (
<>
<div className="row">
{documents.map((document, index) => (
<div className="col-lg-3" key={index}>
<a href={document.filePath}>{document.fileName}</a>
</div>
))}
</div>
</>
);
};
export default Documents;
Thanks,
Jimmy
Simple, just have the parent control document state and pass the state and callback down to the children as a prop. Now the siblings are referencing the same state and will be re-rendered when props (ie document state) changes. The parent can also handle the data fetching and uploading.
it will look like this:
const Parent = () => {
const [documents, setDocuments] = useState([]);
...do data fetching here
const handleSubmit = useCallback(async () => {}, []); // You might want to reset document state here?
return (
<div>
<Docs documents={documents} />
<Upload onUpload={setDocuments} onSubmit={handleSubmit} />
</div>
);
}
I wonder if you should actually have two documents components, one for displaying the files being uploaded, and one for displaying the already uploaded files. You would embed one within the Upload component and the other would fetch documents from the api every time onUpload completes

Resources