Limiting the displayed results in React useEffect - javascript

I have some React code that is filtering my array and displaying the results by what the user searches:
function App() {
const [countries, setCountries] = useState([]);
const [loading, setLoading] = useState(false);
const [search, setSearch] = useState("");
const [filteredCountries, setFilteredCountries] = useState([]);
useEffect(() => {
setLoading(true);
axios
.get("https://www.countriesapi.com")
.then((res) => {
setCountries(res.data);
setLoading(false);
})
.catch((err) => {
console.log(err);
});
}, []);
useEffect(() => {
setFilteredCountries(
countries.filter((country) =>
country.name.toLowerCase().includes(search.toLowerCase())
)
);
}, [search, countries]);
if (loading) {
return <p>Loading countries...</p>;
}
return (
<div className="App">
<h1>Countries Lists</h1>
<a
style={{ color: "red" }}
target="_blank"
href="https://www.somesite.com"
>
</a>
<input
type="text"
placeholder="Search Countries"
onChange={(e) => setSearch(e.target.value)}
/>
{filteredCountries.map((country, idx) => (
<CountryDetail key={idx} {...country} />
))}
</div>
);
}
My countries list has 500 results so having them all in the page before the user writes the whole search term is not feasible.
How can I display let's say only 10 results in the page, rather than the whole 500?
Thanks
Note: My code has been mirrored by this one if you want to take a look at a live example

You could use Array.prototype.slice() to get only 10 elements.
{filteredCountries.slice(0, 10).map((country, idx) => (
<CountryDetail key={idx} {...country} />
))}

You can paginate your list on axios.get("https://www.countriesapi.com") if www.countriesapi.com allows this. Or you can just make countries.slice
useEffect(() => {
const newList = search ? countries : countries.slice(0, 10)
setFilteredCountries(
newList.filter((country) =>
country.name.toLowerCase().includes(search.toLowerCase())
)
);
}, [search, countries]);

Related

How to stop a React App breaking on refreshing when using UseEffect

import {useState, useEffect } from 'react'
import axios from 'axios'
const Singlecountry = ({searchedCountries, setWeather, weather}) => {
const weatherName = searchedCountries[0].capital
const iconname = () => {
if (weather === undefined) {
return null
}
weather.map(w => w.weather[0].icon)
}
console.log(iconname)
useEffect(() => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${weatherName}&appid=${process.env.REACT_APP_API_KEY}`)
.then(response => {
const apiResponse = response.data;
console.log(apiResponse)
console.log(`Current temperature in ${apiResponse.name} is ${apiResponse.main.temp - 273.15}℃`);
setWeather([apiResponse])
}).catch(error => {
console.log(error);
})
}, [])
return(
<div>
capital: {searchedCountries.map(c => <p>{c.capital}</p>)}
area: {searchedCountries.map(c => <p>{c.area}</p>)}
<h2>Languages</h2>
<ul>
{
searchedCountries.map(c =>
<ul>
{Object.values(c.languages).map(l => <li>{l}</li>)}
</ul>
)
}
</ul>
{searchedCountries.map(c => <img src={Object.values(c.flags)[0]} alt="" /> )}
<h3>Weather</h3>
<p>temperature is {weather.map(w => w.main.temp - 273.15)} degrees Celsius</p>
<p>wind is {weather.map(w => w.wind.speed)} miles per hour</p>
<img src={`http://openweathermap.org/img/wn/${iconname}.png`} alt="" />
</div>
)
}
const Countries = ({ searchedCountries, handleClick, show, setWeather, setCountries, weather}) => {
if (weather === undefined) {
return null
}
if (searchedCountries.length >= 10) {
return (
<div>
<p>too many countries to list, please narrow your search</p>
</div>
)
}
if (searchedCountries.length === 1) {
return (
<Singlecountry searchedCountries={searchedCountries} setWeather={setWeather} weather={weather}/>
)
}
if (show === true) {
return (
<Singlecountry searchedCountries={searchedCountries} setWeather={setWeather} />
)
}
return (
<ul>
{searchedCountries.map(c => <li>{c.name.common}<button onClick={handleClick} >show</button></li>)}
</ul>
)
}
const App = () => {
const [countries, setCountries] = useState([])
const [newSearch, setNewSearch] = useState('')
const [show, setShow] = useState(false)
const [weather, setWeather] = useState('')
const handleSearchChange = (event) => {
setNewSearch(event.target.value)
}
const handleClick = () => {
setShow(!show)
}
const searchedCountries =
countries.filter(c => c.name.common.includes(newSearch))
useEffect(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setCountries(response.data)
})
}, [])
return (
<div>
<div><p>find countries</p><input value={newSearch} onChange={handleSearchChange} /></div>
<div>
<h2>countries</h2>
<Countries searchedCountries={searchedCountries} handleClick={handleClick} show={show} setCountries={setCountries} setWeather={setWeather} weather={weather}/>
</div>
</div>
)
}
export default App
The following code is designed to display information on countries when the user types in the countries' name in the search bar, including capital city, temperature and its weather.
The app fetches country data from a Countries API and when the user searches for a specific country, the weather its then fetched from a Weather API.
However, when the app is refreshed, the app breaks when searching for an individual country's weather.
Does anyone know why this is and how to solve it?
Thanks
It looks like you're using axios inside useEffect which can cause and infinite loop and crash your app. I recommend creating a separate function for your data fetching and then call the function in the useEffect like so:
const fetchCountries = useCallback(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setCountries(response.data)
})
}, [])
useEffect(() => {
fetchCountries()
}, [fetchCountries])
The key is the dependency array in useEffect which will only update if there is a change in the list of countries from fetchCountries function, thus preventing the infinite loop.

React.js: filtering API object with 2 search bars

I am fetching an API data set and filtering that data with a search bar to locate by first or last name. I also have an input field that allows you to add "tags" to the data set that I am mapping through. I am trying to add a second search bar to filter the original data by the unique tags as well, but can not figure out how to incorporate that information into the filter.
export default function Home() {
const [students, setStudents] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [search, setSearch] = useState("");
const [showTests, setShowTests] = useState({});
const [tagSearch, setTagSearch] = useState("");
const [tags, setTags] = useState([]);
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(
<!-- API -->
);
setStudents(response.data);
setError(null);
} catch (err) {
setError(err.message);
setStudents(null);
} finally {
setLoading(false);
}
};
getData();
}, []);
return (
<div className="home-main">
<Search setSearch={setSearch} />
<TagSearch setTagSearch={setTagSearch} />
{loading && <div>Loading, please wait ...</div>}
{error && (
<div>{`An Error has occurred. - ${error}`}</div>
)}
<div className="students">
<Fragment>
{
students
&&
students.students.filter((val) => {
if(search === '' || tagSearch === '') {
return val
} else if(val.firstName.toLowerCase().includes(search.toLowerCase())
|| val.lastName.toLowerCase().includes(search.toLowerCase())
|| tags.text.toLowerCase().includes(tagSearch.toLowerCase()) ){
return val
}
}).map(({val}) => (
<!-- additional info -->
<div className="tags">
<Tags setTags={setTags} />
</div>
</div>
</div>
))
}
</Fragment>
</div>
</div>
);
}
This is where the "tag" state is coming from...
export default function Tags({setTags}) {
const [inputText, setInputText] = useState('');
const [tiles, setTiles] = useState([]);
const inputTextHandler = (e) => {
setInputText(e.target.value);
};
const submitTagHandler = () => {
setTiles([
...tiles, {text: inputText, id: Math.floor(Math.random() * 1000000)}
]);
setTags([
...tiles, {text: inputText}
])
setInputText('');
};
return (
<div className="tags-main">
<div className="tiles-contain">
{
tiles.map((obj) => (
<Tiles key={obj.id} text={obj.text} id={obj.id} tiles={tiles} setTiles={setTiles} />
))
}
</div>
<input value={inputText} onChange={inputTextHandler} onKeyPress={(e) => {
if(e.key === 'Enter') {
if(inputText !== "") {
submitTagHandler();
} else {
alert("Please enter a tag")
}
};
}} placeholder='Add Tag Here' type="text" />
</div>
);
}
It works without the tag state added to the filter. After adding the tag logic neither search bar works. How can I add the array of tags to the filter dependency to sort by first or last name and tags?
I'm pretty sure you were getting an error "cannot read toLowerCase of undefined"
You probably wanted to do something like this
tags.some(tag => tag.text.toLowerCase() === tagSearch.toLowerCase())
or
tags.map(tag => tag.text.toLowerCase()).includes(tagSearch.toLowerCase())

Only show matching string in array when using 'useMemo' in react

I have an autocomplete input field and running into issues when initially hiding the output in my array. Instead of showing all my items when mounted, I just want to show the matching string. Leaving my state empty const [filteredRecipes, setFilteredRecipes] = useState(); will throw in error in my loop.
Codesanbox:
https://codesandbox.io/s/quzwg?file=/App.js:251-313
Js:
export default function App() {
const items = useMemo(
() => ["item1", "item2", "anotheritem", "yetanotheritem", "myitem"],
[]
);
const [search, setSearch] = useState("");
const [filteredRecipes, setFilteredRecipes] = useState(items);
const handleSearchChange = event => {
setSearch(event.target.value);
};
useEffect(() => {
setFilteredRecipes(
items.filter(el => el.toLowerCase().includes(search.toLowerCase()))
);
}, [search, items]);
return (
<div>
<input
type="search"
placeholder="type here to filter"
value={search}
onChange={handleSearchChange}
/>
<div>
{filteredRecipes.map(recipe => (
<p>{recipe}</p>
))}
</div>
</div>
);
}
If I'm understanding your question correctly, in your example filteredRecipes are the autocomplete suggestions that you want to initially hide when mounting, or, making an assumption here, whenever the search value is falsey. You can do this by conditionally filtering on search state being truthy/falsey. All strings will include the empty string (''), so you want to handle this case differently.
setFilteredRecipes(
search
? items.filter((el) => el.toLowerCase().includes(search.toLowerCase()))
: []
);
Code
export default function App() {
const items = useMemo(
() => ["item1", "item2", "anotheritem", "yetanotheritem", "myitem"],
[]
);
const [search, setSearch] = useState("");
const [filteredRecipes, setFilteredRecipes] = useState([]);
const handleSearchChange = (event) => {
setSearch(event.target.value);
};
useEffect(() => {
setFilteredRecipes(
search
? items.filter((el) => el.toLowerCase().includes(search.toLowerCase()))
: []
);
}, [search, items]);
return (
<div>
<input
type="search"
placeholder="type here to filter"
value={search}
onChange={handleSearchChange}
/>
<div>
{filteredRecipes.map((recipe) => (
<p>{recipe}</p>
))}
</div>
</div>
);
}

There's a bug with Search and Pagination in React

I'm building my site in React and I have created pagination and search. When I search for something on the site, it only works when after that I go to another page. I think this is due to the fact that Softwares and Pagination are in the same component.
Then I tried lifting-state-up, but I got an error: React Minified Error # 31.
Here's Pagination component:
const Paginator = ({
total, // Total records
startPage = 1,
totalPages = null,
onMovePage = null,
}) => {
...
return (
<>
<section id={styles.paginator}>
<Header/>
...
{range(1, totalPages+1).map(p => (
<PagItem key={p} handleClick={ () => {setCurrentPage(p); onMovePage && onMovePage({currentPage: p})} } title={p} name={p} />
))}
...
</section>
</>
);
};
Here's Softwares component:
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const fetchData = async ({ currentPage }) => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
useEffect(() => {
fetchData({ currentPage: 1 });
}, []);
return (
<>
{
valid &&
<section className={styles.softwares}>
<Header header={"new softwares"} />
{softwares.map(s => (
<Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
))}
<Paginator totalPages={totalPages} total={total} onMovePage={fetchData} />
</section>
}
</>
);
};
SearchForm in Header component:
const Header = ({ handleChange, handleClick }) => {
return (
...
<SearchForm handleChange={handleChange} handleClick={handleClick} />
...
);
};
const SearchForm = ({ style, handleChange, handleClick }) => {
return (
<div style={style}>
<form>
<input
type="text"
onChange={handleChange}
/>
<SearchButton onClick={handleClick} />
<small>ENTER</small>
</form>
</div>
);
};
const SearchButton = ({onClick }) => {
return (
<button type="button" onClick={onClick}>
<FontAwesomeIcon icon={faSearch} />
</button>
);
};
And part of Search in App component:
const App = () => {
...
// Search
const [search, setSearch] = useState('');
const [shouldFetch, setShouldFetch] = useState(false);
const handleChange = (e) => {
setSearch(e.target.value);
}
useEffect(() => {
if (shouldFetch) {
(async () => {
const response = await fetch(`http://127.0.0.1:8000/api/software/?search=${search}`);
const data = await response.json();
setShouldFetch(false);
})()
}
}, [shouldFetch]);
const handleClick = () => setShouldFetch(true);
return (
<div className="App">
<Header handleChange={handleChange} handleClick={handleClick} />
...
<Switch>
<Route path="/" exact render={props => <Softwares {...props} search={search} />} />
</Switch>
{/* Actually I'd like to use Paginator here, but it
throws the error: React Minified Error # 31 */}
...
</div>
);
}
So, how can this be done?
The problem is your useEffect dependencies (or lack thereof).
Here's the relevant section of the code:
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const fetchData = async ({ currentPage }) => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
useEffect(() => {
fetchData({ currentPage: 1 });
}, []);
The empty dependency array means that you are running the effect that calls fetchData one time when the component mounts. Clicks in the Pagination component will call the fetchData function directly. Changes to search do not cause fetchData to re-run. The data depends on the search so search should be a dependency.
The fetchData function is fine in this component. The state that I would recommend lifting up is to lift the currentPage up from Pagination into Softwares. The onMovePage callback can just update the currentPage state. That way you can call fetchData only through your effect and run the effect whenever either search or currentPage changes.
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
// defining the function inside of the useEffect
// lets eslint exhaustive dependency checks work their magic
const fetchData = async () => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
// need to define and call in separate steps when using async functions
fetchData();
}, [currentPage, search]);
return (
...
<Paginator page={currentPage} totalPages={totalPages} total={total} onMovePage={setCurrentPage} />
...
);
};

Can't retrieve the data from this API

I have developed this live search component in react which retrieves data from an API according to the input search value. However it doesn't retrieve or display the data when pointed to this API https://api.itbook.store/1.0/search/program
But when i use an API like for example: http://hn.algolia.com/api/v1/search?query=redux it retrieves data
const [data, setData] = useState({ books: [] });
const [query, setQuery] = useState('program');
const [url, setUrl] = useState(
'https://api.itbook.store/1.0/search/program',
);
useEffect(() => {
const fetchData = async () => {
const result = await axios(url);
setData(result.data);
};
fetchData();
}, [url]);
return(
<Paper className={classes.root}>
<Container maxWidth="lg">
<form className={classes.container} encType="multipart/form-data">
<TextField
required
id="standard-required"
placeholder="Enter Book Name"
label="Search for a Book"
name="bookName"
value={query}
onChange={event => setQuery(event.target.value)}
className={classes.textField}
multiline
rowsMax="2"
margin="normal"/>
<Button onClick={() =>
setUrl(`https://api.itbook.store/1.0/search/${query}`)
}
className={classes.button} color="primary">Search</Button>
<ul>
{data.books.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</form>
</Container>
</Paper>
I want my code to collect the data from the API Json : https://api.itbook.store/1.0/search/something
#sideshowbarker delivers an excellent solution to this problem Trying to use fetch and pass in mode: no-cors
Essentially what you can do to workaround the CORS issue is make your request via CORS Proxy URL.
Here's a working sandbox with updates to your code:
https://codesandbox.io/s/lucid-kapitsa-w1uid
import React, { useState } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import "./styles.css";
const App = () => {
const [url, setUrl] = useState("https://api.itbook.store/1.0/search/");
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const fetchData = async () => {
const proxyURL = "https://cors-anywhere.herokuapp.com/";
const updatedURL = `${proxyURL}${url}${query}`;
const res = await axios(updatedURL);
setResults(res.data.books);
};
const createBookList = () => {
return results.map(book => {
return (
<div>
<h4>{book.title}</h4>
</div>
);
});
};
return (
<div>
<input onChange={e => setQuery(e.target.value)} value={query} />
<button onClick={fetchData}>Click</button>
<div>{createBookList()}</div>
</div>
);
};

Categories

Resources