How should I fetch data onClick in Next js? - reactjs

I have an input field plus a button next to it. I want to fetch data whenever the client presses the button.
I used SWR (I'm not sure if I have to use SWR or getServerSideProps method)
the problem is it fetches data when I refresh the page.
here is my code:
const fetcher = async () => {
const res = await fetch(
'https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=dexter'
);
const data = await res.json();
return data;
};
const { data, error } = useSWR('uk', fetcher);
if (error) {
return 'there was an error';
}
if (!data) {
return 'Loading';
}
console.log(data);
return (
<>
<main>
<div className={style.main_container}>
<NavBar />
<Hero />
</div>
<div className={style.search_container}>
<SearchBar
onChange={(e) => {
setSearchTerm(e.target.value);
console.log(searchTerm);
}}
/>
</div>
<button onClick={?????}>Search!</button>
</main>
</>
);
}

You should call mutate function that useSWR returns you.
const fetcher = async () => {
const res = await fetch(
'https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=dexter'
);
const data = await res.json();
return data;
};
const { data, mutate, error } = useSWR('uk', fetcher);
if (error) {
return 'there was an error';
}
if (!data) {
return 'Loading';
}
console.log(data);
return (
<>
<main>
<div className={style.main_container}>
<NavBar />
<Hero />
</div>
<div className={style.search_container}>
<SearchBar
onChange={(e) => {
setSearchTerm(e.target.value);
console.log(searchTerm);
}}
/>
</div>
<button onClick={() => mutate()}>Search!</button>
</main>
</>
);
}
Check this link please https://swr.vercel.app/docs/mutation

You can do something like this:
import { useState } from 'react';
import useSWR from 'swr';
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const SearchResults = ({ keyword }) => {
const { data, error } = useSWR(
`https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=${keyword}`,
fetcher
);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
if (!data[0]) return <div>not found</div>;
return <div>found {data[0].name}</div>;
};
const Home = () => {
const [startFetching, setStartFetching] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const handleChange = (e) => {
setStartFetching(false);
setSearchTerm(e.target.value);
};
const handleClick = () => {
setStartFetching(true);
};
return (
<>
<label for="series">Series: </label>
<input type="text" value={searchTerm} onChange={handleChange} id="series" />{' '}
<input type="submit" value="Search" onClick={handleClick} />
<br />
{startFetching && <SearchResults keyword={searchTerm} />}
</>
);
};
export default Home;
Basically, create a separate component that handles fetching and displaying UI based on the fetched content. And, create a state in the parent that tells when to render that component (and start fetching).
Credits: https://github.com/vercel/swr/issues/254

Related

Enter the selected item in the search bar in React.js

I have the following code:
function App() {
const [countries,setCountries]= useState([]);
const [search, setSearch] = useState('');
//Take data from API with useEffect, async/await and try/catch
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://restcountries.com/v2/all');
setCountries(response.data);
} catch (error) {
console.error(error);
}
}
fetchData();
}, []);
const filteredCountries = countries.filter((country) =>
country.name.toLowerCase().includes(search.toLowerCase())
);
const handleSelect = (country) => {
setSearch(country.name);
}
return (
<>
<div>
<SearchBar onChange={(e)=> setSearch(e.target.value)} />
{
<ul className="list">
{search.length > 0 && filteredCountries.map((country) => (
<li key={country.name} onClick={() => handleSelect(country)}>
{country.name}
</li>
))}
</ul>
}
</div>
<div className="map-container">
</div>
</>
)
}
export default App;
The result is this:
List image
How can I select an item from the list, e.g. if I search for Ital, Italy appears and I would like to select it and have it appear in the search bar.
I would like to create a search bar to find a country and select it, it should appear in the search bar after being selected.
CodeSandBox Link: https://codesandbox.io/p/github/pierre1590/Population-Tracker/draft/gallant-gagarin?file=%2Fsrc%2Fcomponents%2FMap%2FMap.js
Add value={search} in your <SearchBar/> component.
eg: <SearchBar value={search} onChange={(e)=> setSearch(e.target.value)} />
Below is the full code (I've used a normal input tag in place of your SearchBar component)
import { useState, useEffect } from "react";
import axios from 'axios';
function App() {
const [countries,setCountries]= useState([]);
const [search, setSearch] = useState('');
console.log(search)
//Take data from API with useEffect, async/await and try/catch
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://restcountries.com/v2/all');
setCountries(response.data);
} catch (error) {
console.error(error);
}
}
fetchData();
}, []);
const filteredCountries = countries.filter((country) =>
country.name.toLowerCase().includes(search.toLowerCase())
);
const handleSelect = (country) => {
setSearch(country.name);
}
return (
<>
<div>
<input value={search} onChange={(e)=> setSearch(e.target.value)} />
{
<ul className="list">
{search.length > 0 && filteredCountries.map((country) => (
<li key={country.name} onClick={() => handleSelect(country)}>
{country.name}
</li>
))}
</ul>
}
</div>
<div className="map-container">
</div>
</>
)
}
export default App;
CodeSandBox Link - https://codesandbox.io/s/enter-the-selected-item-in-the-search-bar-in-react-js-582rez?file=/src/App.js

Fetching data on second click problem! React js Next js

When I click on the search button the first time API response is "undefined" (based on what console.log says) but the second time it has the response from API.
Why does this happen?
xport default function Home() {
const [searchTerm, setSearchTerm] = useState('');
const fetcher = (url) => fetch(url).then((res) => res.json());
const [shouldFetch, setShouldFetch] = useState(false);
const { data, error } = useSWR(
() =>
shouldFetch
? `https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=${searchTerm}`
: null,
fetcher
);
if (error) return 'An error has occurred.';
return (
<>
<main>
<div className={style.main_container}>
<NavBar />
<Hero />
</div>
<div className={style.search_container}>
<SearchBar
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<button
onClick={() => {
setShouldFetch(true);
console.log(searchTerm);
console.log(data);
}}
>
Search!
</button>
</div>
</main>
</>
);
}

React Router: Navigate back to Search results

I have dynamic routes based on search results. How do I go back and see my previously rendered search results & search term in input field versus and empty Search page?
I've started looking into useHistory/useLocation hooks, but I'm lost.
1. Search page
export default function Search() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [noResults, setNoResults] = useState(false);
const [data, setData] = useState([]);
const fetchData = async () => {
const res = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key={API_KEY}&query=${searchValue}`
);
const data = await res.json();
const results = data.results;
if (results.length === 0) setNoResults(true);
setData(results);
setIsLoading(false);
};
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
fetchData();
// setSearchValue("");
}
return (
<div className="wrapper">
<form className="form" onSubmit={handleSubmit}>
<input
placeholder="Search by title, character, or genre"
className="input"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
</form>
<div className="page">
<h1 className="pageTitle">Explore</h1>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="results">
{!noResults ? (
data.map((movie) => (
<Result
poster_path={movie.poster_path}
alt={movie.title}
key={movie.id}
id={movie.id}
title={movie.title}
overview={movie.overview}
release_date={movie.release_date}
genre_ids={movie.genre_ids}
/>
))
) : (
<div>
<h1 className="noResults">
No results found for <em>"{searchValue}"</em>
</h1>
<h1>Please try again.</h1>
</div>
)}
</div>
)}
</div>
</div>
);
}
2. Renders Result components
export default function Result(props) {
const { poster_path: poster, alt, id } = props;
return (
<div className="result">
<Link
to={{
pathname: `/results/${id}`,
state: { ...props },
}}
>
<img
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
</Link>
</div>
);
}
3. Clicking a result brings you to a dynamic page for that result.
export default function ResultPage(props) {
const [genreNames, setGenreNames] = useState([]);
const {
poster_path: poster,
overview,
title,
alt,
release_date,
genre_ids: genres,
} = props.location.state;
const date = release_date.substr(0, release_date.indexOf("-"));
useEffect(() => {
const fetchGenres = async () => {
const res = await fetch(
"https://api.themoviedb.org/3/genre/movie/list?api_key={API_KEY}"
);
const data = await res.json();
const apiGenres = data.genres;
const filtered = [];
apiGenres.map((res) => {
if (genres.includes(res.id)) {
filtered.push(res.name);
}
return filtered;
});
setGenreNames(filtered);
};
fetchGenres();
}, [genres]);
return (
<div className="resultPage">
<img
className="posterBackground"
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
<div className="resultBackground">
<div className="resultInfo">
<h1> {title} </h1>
</div>
</div>
</div>
);
}
4. How do I go back and see my last search results?
I'm not sure how to implement useHistory/useLocation with dynamic routes. The stuff I find online mentions building a button to click and go to last page, but I don't have a button that has to be clicked. What is someone just swipes back on their trackpad?
One way you could do this would be to persist the local component state to localStorage upon updates, and when the component mounts read out from localStorage to populate/repopulate state.
Use an useEffect hook to persist the data and searchValue to localStorage, when either updates.
useEffect(() => {
localStorage.setItem('searchValue', JSON.stringify(searchValue));
}, [searchValue]);
useEffect(() => {
localStorage.setItem('searchData', JSON.stringify(data));
}, [data]);
Use an initializer function to initialize state when mounting.
const initializeSearchValue = () => {
return JSON.parse(localStorage.getItem('searchValue')) ?? '';
};
const initializeSearchData = () => {
return JSON.parse(localStorage.getItem('searchData')) ?? [];
};
const [searchValue, setSearchValue] = useState(initializeSearchValue());
const [data, setData] = useState(initializeSearchData());

NextJS getStaticProps() not updating from form values

After submitting with UpdateParams, the new url is called and a JSON object with the new queried data is returned as expected.
The form updates the two state vars.
However, the products in the all-products view are not updated to reflect the form input.
What do I need to do to to refresh the render to reflect the new data in product?
//all-products.js
import Link from 'next/link'
import React from 'react';
import { useState } from 'react';
//gets data from local api
async function getData(rank, keyword){
const res = await fetch(`http://localhost:4000/api?top=${rank}&keyword=${keyword}`);
return res;
}
export async function getStaticProps() {
const rank = 5;
const keyword = "shorts";
const response = await getData(rank, keyword);
const products = await response.json();
console.log(products);
if (!products) {
return {
notFound: true,
}
}
return {
props: {
products,
},
}
}
export default function AllProducts(stuff) {
let {products} = stuff;
const [rank, setRank] = useState("3");
const [keyword, setKeyword] = useState("shoes");
//from form
const updateParams = async (e) => {
e.preventDefault();
const response= await getData(rank, keyword);
products = await response.json();
}
return (
<div>
<input
type='text'
placeholder='topRank'
value={rank}
onChange={e => setRank(e.target.value)}
/>
<input
type="text"
placeholder='searchTerm'
value={keyword}
onChange={e => setKeyword(e.target.value)}
/>
<button
type='submit'
onClick={updateParams}>
Update Params</button>
<ul>
{products.Products.map((product) => {
return (
<div key={product.Id}>
<li>{product.Name}</li>
<li><img width={300} src={ product.imgUrl } alt="product image" /></li>
</div>
) }
)}
</ul>
</div>
)
}
getStaticProps is run at build-time so it'll provide the data that's available at that time. To update the UI after the user interacts with the form you should put products into state and update it once new params are submitted and you retrieve the new products.
// all-products.js - removed irrelevant code for simplicity
export default function AllProducts(stuff) {
const [products, setProducts] = useState(stuff.products);
//...
const updateParams = async (e) => {
e.preventDefault();
const response = await getData(rank, keyword);
const newProducts = await response.json();
setProducts(newProducts);
}
return (
//...
<ul>
{products.Products.map((product) => {
return (
<div key={product.Id}>
<li>{product.Name}</li>
<li><img width={300} src={product.imgUrl} alt="product image" /></li>
</div>
)
})}
</ul>
//...
)
}

How to implement fetch API by search

I have a search bar. when I enter the name of any film, filtered data has to be displayed (as a result of fetching from API)
function App() {
const [films, setFilms] = useState([])
const [searchText, setSearchText] = useState('')
const [filteredRes, setFilteredRes] = useState([])
const url = `http://www.omdbapi.com/?apikey=KEY&s=${searchText}`
useEffect (()=> {
loadData()
}, [searchText])
const loadData = async () => {
const res = await axios.get(url)
setFilms(res.data.Search)
const filtered = await films.filter(i => i.toLowerCase().includes(searchText))
setFilteredRes(filtered)
}
const onTextChange = (e) => {
setSearchText(e.target.value)
}
return (
<>
<Container>
<h1>Bookstore</h1>
<Row>
<form>
<input type='text'
placeholder='Search...'
name="searchText"
onChange={onTextChange}
value={searchText}
/>
</form>
</Row>
<Row>
{ filteredRes.map(item => {
return (
<Col lg={3} md={3} sm={12} key={item.imdbID}>
<img src={item.poster}/>
<h6>{item.title}</h6>
<h6>{item.year}</h6>
</Col>
)
})}
</Row>
</Container>
</>
);
}
I have am issue:
Unhandled Rejection (TypeError): Cannot read property 'filter' of undefined. But why if I save fetched films in const [ films ] ?
Ooutput:
You don't have to filter fetched data additionally, the data that is being fetched by Axios is already filtered according to the serchText so Axios call does the all.
Full Example:
import React, { useState } from "react";
import Axios from "axios";
const APIKEY = "your api";
export default function App() {
const [searchText, setSearchText] = useState("");
const [films, setFilms] = useState([]);
const handleSubmit = (event) => {
event.preventDefault();
if (!searchText) {
alert("Please enter movie name");
return;
}
Axios.get(
`http://www.omdbapi.com/?i=tt3896198&apikey=${APIKEY}&s=${searchText}`
)
.then((response) => {
console.log(response.data);
setFilms(response.data.Search);
})
.catch((error) => {
console.log(error);
});
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<input
placeholder={"Search movie"}
onChange={(event) => {
setSearchText(event.target.value);
}}
/>
<button>Search</button>
</form>
{films.map((film) => (
<div>
<img src={film["Poster"]} alt={film["Title"]} width={100} />
<p>{film["Title"]}</p>
<p>{film["Year"]}</p>
</div>
))}
</div>
);
}
Github Repo

Resources