Search bar not displaying results in React - reactjs

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 ))

Related

How to render form after submission in react?

Given the following form, I need whenever the form is submitted, the new post to be listed/rendered without having to refresh the page.
const PostCreate = () => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I tried adding this.forceUpdate() and this.setState(this.state), neither works, and I still have to refresh the page for the new post to show.
Here's how the posts are rendered:
const PostList = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
This is what App.js looks like
const App = () => {
return <div>
<h1>Create Post</h1>
<PostCreate></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList></PostList>
</div>;
};
export default App;
and is eventually rendered using:
ReactDOM.render(
<App></App>,
document.getElementById('root')
)
In your PostList, useEffect called once when you first load your component, so when you create new post, it will not be re-rendered
You should bring your fetchPost logic to your App component, and add function props onPostCreated to PostCreate component, trigger it after you finish creating your new post
The code should be:
const App = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
return <div>
<h1>Create Post</h1>
<PostCreate onCreatePost={() => fetchPost()}></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList posts={posts}></PostList>
</div>;
};
export default App;
const PostList = ({ posts }) => {
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
const PostCreate = ({ onCreatePost }) => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
onCreatePost && onCreatePost();
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I think the problem you are having is not in the code you have displayed. The component is indeed rerendering after you change its state and also when you forceUpdate() it. I assume the posts you are trying to display are taken from the same API that you post to. Even if this component is being rerendered, your GET request which gives the data to the component who renders it is not called again so the data doesn't update. You need to refetch it. This can be done by many different ways (useEffect(), callbacks, reactQuery refetch) depending on the rest of your code. I would need the component that renders the data and the API call to help you further.
Another thing that you didn't ask but is good practice. In your PostCreate component you don't need to manage the state of fields that are in the form, because it already does it for you. Just give a name to your inputs and use the form data. I've given an example below.
import { useState } from "react";
const PostCreate = () => {
const onSubmit = async (event) => {
event.preventDefault();
console.log(event.target.elements.title.value);
};
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input name="title" className="form-control" />
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>
);
};
export default PostCreate;

How to hide the settings div after receving an input from the user ? in a React app

I have a simple Quiz app that fetches the questions from an API (https://opentdb.com/api.php?amount=100), filters them by difficulty, and renders the questions in the 'Questions' component.
I want to hide the settings after filtering - when the questions are shown.
This is my code:
import React, { useEffect, useState } from "react";
import Questions from "./Questions";
const Welcome = () => {
/*Fetch questions*/
const questionsAPI = "https://opentdb.com/api.php?amount=100";
const [questionsFromAPI, setQuestionsFromAPI] = useState([]);
const [difficultyValue, setDifficulty] = useState("");
const [filteredQ, setFilteredQ] = useState([]);
const handleDifficultyChange = (event) => {
setDifficulty(event.target.value);
};
const handleSearchReset = () => {
setDifficulty("");
};
const fetchData = () => {
return fetch(questionsAPI)
.then((response) => response.json())
.then((data) => setQuestionsFromAPI(data.results));
};
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
setFilteredQ(
questionsFromAPI.filter((q) => q.difficulty === difficultyValue)
);
}, [questionsFromAPI, difficultyValue]);
return (
<div>
<h1>QUIZ</h1>
**<div classname="setting">
<h1>Search Posts</h1>
<br />
<p>difficulty</p>
<input
type="string"
min={"difficulty"}
value={difficultyValue}
onChange={handleDifficultyChange}
/>
<br />
<button classname="button" type="button" onClick={handleSearchReset}>
Reset Search
</button>
</div>**
{filteredQ.length > 0 ? (
<div>
<Questions questionsAPI={filteredQ}></Questions>
</div>
) : (
<br></br>
)}
</div>
);
};
export default Welcome;
In React elements have the attribute hidden, you can use that to hide your settings div. For example by using a hook:
const [isHidden, hideSettings] = useState(false)
And to the settings div add the following
<div hidden={isHidden} classname="setting">
and use hideSettings(true) after filtering

react js myfn is not a function when called from a button

I've just started learning about react js and this is my first react js app. I'm using api to fetch the data. so far it works, but now I want to add a search keyword to the function that is acquired from a search bar component.
here's my code:
SearchBar.js
const SearchBar = ({ getUsers }) => {
return (
<div className="is-flex flex-align-items-center mb-3">
<input type="text" id="query" className="input search-input" placeholder="search keyword"/>
<Button className="search-btn ps-3 pe-3"
onClick={() => getUsers(document.querySelector('#query').value)}>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</Button>
</div>
);
};
MasterUser.js
import { useState, useEffect } from "react";
import SearchBar from "./SearchBar";
const MasterUser = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
getUsers();
}, []);
const getUsers = async (query='') => {
console.log('get users', query);
try {
let myurl = 'http://localhost:8080/users';
const response = await fetch(myurl);
const data = await response.json();
setUsers(data);
setIsLoading(false);
} catch (e) {
console.log(e.getMessage());
}
};
return (
<div>
<SearchBar onClick={getUsers}/>
</div>
);
};
when the app loads, the console log says get users <empty string> and it returns all the users as expected, but when I clicked on the search button (magnifyingGlass) it gives an error Uncaught TypeError: getUsers is not a function.
any help is appreciated..
<SearchBar onClick={getUsers}/>
You have named the prop onClick not getUsers. That's why you get that error.
Yeah, accessing dom element value using selectors (e.g. document.querySelector('#query').value) is also not typical react. Read about controlled form elements (save form element value in state).
Make your searchBar component more reactive like so
const SearchBar = ({ getUsers }) => {
const [searchValue,setSearchValue]=useState('');
return (
<div className="is-flex flex-align-items-center mb-3">
<input type="text" id="query" className="input search-input" placeholder="search keyword" value={searchValue} onChange={(e)=>setSearchValue(e.target.value)}/>
<Button className="search-btn ps-3 pe-3"
onClick={() => getUsers(searchValue)}>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</Button>
</div>
);
};

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

Creating a history page with React Hooks

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]);

Resources