The problem:
I want to show a (no results found) after doing a search and didn't found anything, I know that I should handle errors but I didn't know how! Beginners thing.
The Code:
first I fetched the data, then I settled as APIData, then when user is typing it start to filter the results, but i want to show a (no results found).
export default function SearchInput() {
const [APIData, setAPIData] = useState([]);
const [searchInput, setSearchInput] = useState('');
const [filteredResults, setFilteredResults] = useState([]);
const [error, setError] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const fetchData = async () => {
setError(false);
setIsLoaded(true);
try{
const response = await axios("/posts");
setAPIData(response.data);
} catch(error) {
setError(true);
console.error(error);
}
setIsLoaded(false);
};
fetchData();
}, []);
console.log(APIData)
const searchItems = (searchValue) => {
setSearchInput(searchValue)
if (searchInput !== '') {
const filteredData = APIData.filter((post) => {
return Object.values(post).join('').toLowerCase().includes(searchInput.toLowerCase())
})
setFilteredResults(filteredData)
}
else{
setFilteredResults(APIData)
}
}
return (
<div style={{ padding: 20 }}>
<OutlinedInput
className="SearchInput"
placeholder='Search...'
onChange={(e) => searchItems(e.target.value)}
endAdornment={
<InputAdornment>
<SearchIcon />
</InputAdornment>
}
/>
<div style={{ marginTop: 20 }}>
{searchInput.length > 0 ? (
filteredResults.map((post) => {
return (
<div className="card" key={post._id}>
<div className="card-content">
<Link to={`/post/${post._id}`} className="link">
<h2 className="results-title">
{post.title}
</h2>
<ol className="card-username">
{post.username}
</ol>
</Link>
</div>
</div>
)
})
) : (
APIData.map((post) => {
return (
<article className="card" key={post._id}>
<div className="card-content">
<Link to={`/post/${post._id}`} className="link">
<h2 className="card-name">
{post.title}
</h2>
<ol className="card-username">
{post.username}
</ol>
</Link>
</div>
</article>
)
})
)}
</div>
</div>
Related
In my project I want to access the name of student after clicking the present button .
So it means if I click on "arijit" name then it should log on console "arijit"
const StudentList = () => {
const [studentlist, setStudentList] = useState([]);
const studentdataList = async () => {
const result = await fetch("http://localhost:5000/api/students");
const studentResult = await result.json();
setStudentList(studentResult);
console.log(studentResult[0].name);
};
function present(){
// what to write here?
}
useEffect(() => {
studentdataList();
},[]);
return (
<div>
{studentlist.map((student) => {
return (
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title> {student.name} </Card.Title>
<Button onClick={present}>present</Button>
<Button>abscent</Button>
</Card.Body>
</Card>
);
})}
</div>
);
};
export default StudentList;
The simplest solution would be to pass the student's name into the present function inside of the onClick handler.
const StudentList = () => {
const [studentlist, setStudentList] = useState([]);
const studentdataList = async () => {
const result = await fetch("http://localhost:5000/api/students");
const studentResult = await result.json();
setStudentList(studentResult);
console.log(studentResult[0].name);
};
function present(name){
console.log(name);
}
useEffect(() => {
studentdataList();
},[]);
return (
<div>
{studentlist.map((student) => {
return (
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title> {student.name} </Card.Title>
<Button onClick={() => present(student.name)}>present</Button>
<Button>abscent</Button>
</Card.Body>
</Card>
);
})}
</div>
);
};
export default StudentList;
i've got a problem. I'm trying to make data search from API.
The problem is that {people.length === 0 && <p>No data</p>} is not working. When i console.log people.length the length value doesn't change when i'm typing. Where's the problem?
Here's my code:
const PeopleSearch = () => {
const [people, setPeople] = useState([]);
const [search, setSearch] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const fetchData = async () => {
const response = await axios.get(swapi);
const transformedPeople = response.data.results.sort((a, b) =>
a.name.localeCompare(b.name)
);
setPeople(transformedPeople);
setLoading(false);
};
fetchData();
}, []);
const searchHandler = (event) => {
setSearch(event.target.value);
};
return (
<>
<Input
type="text"
placeholder="Search by name..."
onChange={searchHandler}
/>
<section>
{loading ? (
<Loading />
) : (
<PeopleList
people={people.filter(({ name }) => {
if (name.toLowerCase().includes(search.toLowerCase())) {
return people;
}
})}
/>
)}
{people.length === 0 && <p>No data</p>}
</section>
</>
);
};
You're only calling setPeople once, on mount, with the useEffect(() => { ... }, []) - so people.length will remain the same once the API call completes. You need to use the filtered array both for the PeopleList prop and the No data check.
You should also tweak your filter, since it only cares about whether its return value is truthy or not.
const filteredPeople = people.filter(({ name }) => name.toLowerCase().includes(search.toLowerCase()));
return (
<>
<Input
type="text"
placeholder="Search by name..."
onChange={searchHandler}
/>
<section>
{loading ? (
<Loading />
) : (
<PeopleList
people={filteredPeople}
/>
)}
{filteredPeople.length === 0 && !loading && <p>No data</p>}
</section>
</>
);
Hi I am getting TypeError: Cannot read properties of undefined (reading 'preventDefault') on both my fetches. I have a usEffect that is being used to set the pagination. Which is where the problem seems to be but I'm not sure how I can solve this. When I remove the useEffect the search works but the pagination doesn't so I am a bit confused as to why I am getting the error.
Any help is appreciated.
Thanks
import React, {useState, useEffect} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Alert, Col, Row, ListGroup, Container } from 'react-bootstrap';
import { MovieCard } from '../Components/MovieCard';
import { CustomPagination } from '../Components/CustomPagination';
export const Search = () => {
const [page, setPage] = useState(1)
const [numOfPages, setNumOfPages] = useState();
const [query, setQuery] = useState("");
const [movies, setMovies] = useState([]);
const [show, setShow] = useState(true);
// eslint-disable-next-line
const [sayt, setSayt] = useState([]);
const fetchResults = e => {
e.preventDefault();
setQuery(e.target.value);
fetch(`https://api.themoviedb.org/3/search/movie?api_key=${process.env.REACT_APP_TMDB_KEY}&page=${page}&language=en-US&include_adult=false&query=${e.target.value}`
)
.then((res) => res.json())
.then((data) => {
if (!data.errors) {
console.log(data)
setSayt(data.results);
}else{
<Alert variant="danger">Error</Alert>
}
})
}
const fetchSearch = e => {
e.preventDefault();
setQuery(e.target.value);
fetch(`https://api.themoviedb.org/3/search/movie?api_key=${process.env.REACT_APP_TMDB_KEY}&page=${page}&language=en-US&include_adult=false&query=${e.target.value}`
)
.then((res) => res.json())
.then((data) => {
if (!data.errors) {
console.log(data)
setMovies(data.results);
setNumOfPages(data.total_pages);
}else{
<Alert variant="danger">Error</Alert>
}
});
setShow((s) => !s)
}
useEffect (()=>{
fetchSearch();
fetchResults();
// eslint-disable-next-line
}, [page])
const saytShow = e => {
setShow((s) => !s)
}
return (
<div>
<div className="search-container">
<div className="row height d-flex justify-content-center align-items-center">
<div className="col-xs-12 col-sm-12 col-md-8 col-lg-8 my-4">
<form fetchSearch={fetchSearch} value={query}>
<div className="search"> <i className="fa fa-search" onClick={saytShow}></i>
<input
type="text"
className="form-control"
placeholder="Search for a movie..."
onChange={fetchResults}
onClick={saytShow}
/>
{sayt.length > 0 && (
<ListGroup className="sayt" style={{ display: show ? "block" : "none" }}>
{sayt.map(suggestions => (
<ListGroup.Item action type="submit" onClick={fetchSearch} value={suggestions.title}>
{suggestions.title}
</ListGroup.Item>
))}
</ListGroup>
)}
<button type="submit" onClick={fetchSearch} value={query} className="search-button btn btn-primary">Search</button>
</div>
</form>
</div>
</div>
</div>
<Container fluid className="movie-grid">
{movies.length > 0 && (
<Row>
{movies.map(movieresults => (
<Col className="movie-grid" key={movieresults.id}>
<MovieCard movieresults={movieresults}/>
</Col>
))}
</Row>
)}
</Container>
<CustomPagination setPage={setPage} numOfPages={setNumOfPages} />
</div>
)
}
When you call fetchResults fetchSearch in useEffect you haven't event object. Just write correct conditions. Example:
const fetchResults = e => {
e && e.preventDefault() && setQuery(e.target.value);
fetch(`YOUR_URL?&query=${e ? e.target.value : query}`)
.then((res) => res.json())
.then((data) => {
if (!data.errors) {
console.log(data)
setSayt(data.results);
}else{
<Alert variant="danger">Error</Alert>
}
})
}
the same with fetchSearch function
Try change the button type from submit to button.
<button type="button" onClick={fetchSearch} value={query} className="search-button btn btn-primary">Search</button>
This way you don't need to prevent the default function of type submit.
I am having trouble in adding Infinite Scrolling in my Home.js to display posts. If i display all the posts directly then on each refresh a no of read counts are added to my database(Firebase). I want to limit a display to 10-12 posts and display next according to the scrolling. How and where to add the infinite scrolling code and on what to add. Here is my static Home.js. On the code below all the posts are being displayed directly:
function Home() {
const classes = useStyles();
const [modalStyle] = useState(getModalStyle);
const [posts, setPosts] = useState([]);
const [open, setOpen] = useState(false);
const [openSignIn, setOpenSignIn] = useState(false);
const [username, setUserName] = useState('');
const [password, setPassword] = useState('');
const [email, setEmail] = useState('');
const [user, setUser] = useState('null');
const [image, setImage] = useState(null);
const [progress, setProgress] = useState(0);
const [url, setUrl] = useState("");
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((authUser) => {
if (authUser){
console.log(authUser);
setUser(authUser);
}
else {
setUser(null)
}
})
return() =>{
unsubscribe();
}
}, [user, username]);
useEffect(() => {
db.collection('posts').orderBy('timestamp','desc').onSnapshot(snapshot => {
setPosts(snapshot.docs.map(doc =>({
id: doc.id,
post: doc.data()
})));
})
}, [])
return (
<div className="app">
<div className="app_header">
<img className="app_headerImage"
src="https://firebasestorage.googleapis.com/v0/b/inconnect-b0ef1.appspot.com/o/dp%2FLOGO786.png?alt=media&token=adeb1a58-5085-4a3f-b33a-9b6d8078624c"
/>
<div className="app_loginContainer">
<Button onClick={() => auth.signOut()}><Link to='/' >Logout </Link><BsHouseDoorFill/> </Button>
<a>
<Button > <Link to='/Feed' > Feed</Link> <BsHouseDoorFill/> </Button>
<Button ><Link to='/Profile' > Profile</Link> <BsHouseDoorFill/> </Button>
<br>
</br>
<br>
</br>
<Avatar
className="post_avatar"
alt='inconnect'
src="https://i2-prod.mirror.co.uk/incoming/article6393590.ece/ALTERNATES/s615/Daniel-Craig-in-a-new-photoshoot-to-announce-the-release-of-tickets-Bond-film-Spectre.jpg"
/><h3>Hi {user.displayName}! </h3>
</a>
</div>
</div>
{user?.displayName ? (
<Modal className="photodiv"
open={open}
onClose={() => setOpen(false)}>
<div style={modalStyle} className={classes.paper}>
<ImageUpload username={user.displayName} />
</div>
</Modal>
):
( <h3>Sorry you need to login to upload and comment</h3>
)}
<div className="topload">
<h1 onClick={() => setOpen(true)}> Wanna post {user.displayName}?</h1>
</div>
<Search />
<div className="app_posts">
<div className="app_postsLeft">
{
posts.map(({id, post}) =>(
<Post key={id} postId={id} user= {user} username={post.username} caption={post.caption} imageUrl={post.imageUrl}/>
))
}
</div>
</div>
</div>
);
}
export default Home;
You can use Intersection observer
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
Create component and emit events 'show' or 'hide' - depend on whether observer interacts viewport or not
Sorry, for my bad English, but I hope, you cought me)
I have made a rudimentary recipe searching app in React, the data received from the API is displayed in recipe cards in the Recipe component. I want to add buttons which once click filter the results to display the recipes cards with the Vegan healthLabel.
This is the App component which interacts with the API. I'm stuck on how to get the results to only display the data with a certain label on click.
const App = () =>
const APP_ID = '072f4029';
const APP_KEY = '1e1f9dc0b5c22bdd26363da4bbaa74b8';
const [recipes, setRecipes] = useState([]);
const [search, setSearch] = useState('');
const [query, setQuery] = useState('');
useEffect(() => {
getRecipes();
}, [query])
const getRecipes = async () => {
const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}&from=0&to=12`)
const data = await response.json();
setRecipes(data.hits);
}
const updateSearch = e => {
setSearch(e.target.value);
}
const getSearch = e => {
e.preventDefault();
setQuery(search);
setSearch('');
}
const props = useSpring({ opacity: 1, from: { opacity: 0 } })
return (
<div className='App'>
<div className="header">
<div className="logo">
<img className="knife" src={logo} alt="Logo" />
<h1>Recipe Finder</h1>
</div>
</div>
<form onSubmit={getSearch} className="search-form">
<InputGroup>
<InputGroupAddon addonType="prepend">
<InputGroupText><FontAwesomeIcon icon={faSearch} /></InputGroupText>
</InputGroupAddon>
<Input className="search-bar" type="text" placeholder="Search for recipe..." value={search} onChange={updateSearch} />
</InputGroup>
<Button color="primary" size="sm" className="search-button" type="submit">Search</Button>
</form>
<UncontrolledAlert className="alert" color="info">
sambailey.dev
</UncontrolledAlert>
<div style={props} className="recipes">
{recipes.map(recipe => (
<Recipe
key={recipe.recipe.label}
title={recipe.recipe.label}
theUrl={recipe.recipe.url}
image={recipe.recipe.image}
ingredients={recipe.recipe.ingredients}
source={recipe.recipe.source}
healthLabels={recipe.recipe.healthLabels}
servings={recipe.recipe.yield} />
))}
</div>
</div>
);
}
export default App;
This is the Recipe card component
const Recipe = ({ title, theUrl, image, ingredients, source, healthLabels, servings, deleteRecipe }) => {
const [modal, setModal] = useState(false);
const toggle = () => setModal(!modal);
const down = <FontAwesomeIcon icon={faSortDown} />
const zoom = <FontAwesomeIcon onClick={toggle} className={style.maximise} icon={faSearchPlus} />
const Heart = styled(Checkbox)({
position: 'absolute',
top: 1,
right: 1,
});
return (
<div className={style.recipe}>
<Heart className={style.heart} icon={<FavoriteBorder />} checkedIcon={<Favorite />} name="checkedH" />
<div className={style.top}>
<h6>{title}</h6>
<Badge className={style.badge} color="primary">{source}</Badge>
<p>Serves: <Badge color="primary" pill>{servings}</Badge></p>
<div className={style.imageContainer}>
<img onClick={toggle} src={image} alt='food' />
{zoom}
</div>
<Modal isOpen={modal} toggle={toggle}>
<img src={image} alt="" className={style.maxi} />
</Modal>
</div>
<ol className={style.allergens}>
{healthLabels.map(healthLabel => (
<li>{healthLabel}</li>
))}
</ol>
<div className={style.ingr}>
<p className={style.inghead} id="toggler">Ingredients <Badge color="secondary">{ingredients.length}</Badge> {down}</p>
<UncontrolledCollapse toggler="#toggler">
<ol id="myol">
{ingredients.map(ingredient => (
<li className={style.customList}>{ingredient.text}</li>
))}
</ol>
</UncontrolledCollapse>
<Button className={style.button} outline color="primary" size="sm" href={theUrl} target="_blank">Method</Button>
</div>
<div className={style.info}>
<div className={style.share}>
<WhatsappShareButton url={theUrl}><WhatsappIcon round={true} size={20} /></WhatsappShareButton>
<FacebookShareButton url={theUrl}><FacebookIcon round={true} size={20} /></FacebookShareButton>
<EmailShareButton url={theUrl}><EmailIcon round={true} size={20} /></EmailShareButton>
</div>
</div>
</div >
);
}
export default Recipe;
Your useEffect is already dependent on the query property. To trigger a new fetch, you could set the state of the query parameter to the one you want to fetch:
label onclick pseudocode:
export default function Recipe({ onLabelClick, label }) {
return (
<div onClick={onLabelClick}>
{label}
</div>
);
}
You can then load a Recipe like so:
<Recipe
onLabelClick={() => setQuery("what you want your new query to be")
label={recipe.recipe.label}
/>
When clicked, the query property will be updated and the useEffect will be triggered as a result. This will lead to a new fetch!
[EDIT] The OP asked also for an example on how to filter already loaded recipes:
// Let's assume a recipe has a property "title"
const [recipes, setRecipes] = useState([]);
const [filter, setFilter] = useState("");
const [filteredRecipes, setFilteredRecipes] = useState([]);
useEffect(() => {
if (filter) {
const newFilteredRecipes = recipes.filter(recipe => recipe.title.toLowerCase().includes(filter.toLowerCase()));
setFilteredRecipes(newFilteredRecipes);
}
}, [recipes, filter]);
return (
<>
{filteredRecipes.map((recipe, index) => {
return <Recipe
key={index}
onLabelClick={() => setQuery("what you want your new query to be")
label={recipe.recipe.label}
/>
}
}
</>
);