NB: Someone please explain how to format my code here, it just doesn't work, Im doing it wrong or something.
Here is my code after refactoring according to comments, it now works!
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const App = () => {
const [countries, setCountries] = useState([])
useEffect(() => {
console.log('effect');
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
console.log('promise fulfilled');
const countries = response.data
setCountries(countries)
})
}, [])
const [searchVal, setSearchVal] = useState('')
const searchOnChangeEventHandler = (event) => setSearchVal(event.target.value)
const filteredCountries = (searchVal, countries) => countries.filter(country => country.name.toLowerCase().includes(searchVal.toLowerCase()))
const filteredCountriesLength = filteredCountries(searchVal, countries).length
return (
<div>
<h1>coutries data</h1>
<div>
<input
type='text'
placeholder='search countries'
value={searchVal}
onChange={searchOnChangeEventHandler}
>
</input>
</div>
<div>
<>
{
countries.length
? (searchVal
? (
filteredCountriesLength <= 10 && filteredCountriesLength > 1
? filteredCountries(searchVal, countries).map(country => <p key={country.name}>{country.name}</p>)
: <span>{filteredCountriesLength} matches! too many matches, specify another filter...</span>
)
: <span>type to search..</span>)
: <span>populating countries data...</span>
}
</>
</div>
</div >
)
}
export default App
The following piece now works the way I wanted it to, after I cleaned up the code, I think its better to use if-else and then convert to ternary as suggested.
<div>
<>
{
countries.length
? (searchVal
? (
filteredCountriesLength <= 10 && filteredCountriesLength > 1
? filteredCountries(searchVal, countries).map(country => <p key={country.name}>{country.name}</p>)
: <span>{filteredCountriesLength} matches! too many matches, specify another filter...</span>
)
: <span>type to search..</span>)
: <span>populating countries data...</span>
}
</>
</div>
NB: Is there a way to strip all this white-space in the code?
I wanted to print type to search whenever the searchVal input was empty.
I'm not completely sure about what you're asking for but I think it would help you by moving the ternaries into a function like:
const handleRender = () => {
const length = filteredCountries(searchVal, countries)?.length
if (length) {
if (length <= 10) {
return filteredCountries(searchVal, countries).map(country => <p key={country.name}>{country.name}</p>)
} else {
return <span>please be more specific, add filter...</span>
}
}
return <span>populating countries data...</span>
}
// render
{handleRender()}
Related
I have an issue with my code, i have a search input and a list of countries
When i type some words i have an error which cause to my app collapse
I've been trying for about two days to find the problem but can't find it.
This is the error message : Uncaught TypeError: Cannot read properties of undefined (reading 'filter')
const Country = ({name, num}) =>{
//console.log(name)
return (
<div>
<p>{name}</p>
</div>
)} // Component
const Input = ({onSearch, search}) =>{
return (
<div>
Find countries: <input onChange={onSearch} value={search} />
</div>
)} // Component
import { useState, useEffect } from "react";
import axios from "axios";
import Input from "./components/Input";
import Country from "./components/Country";
const App = () => {
const [countryList, setCountryList] = useState();
const [search, setSearch] = useState("");
const [filter, setFilter] = useState(false);
useEffect(() => {
axios
.get("https://restcountries.com/v3.1/all")
.then((res) => setCountryList(res.data));
}, []);
const onSearch = (event) => {
if (event.target.value === " ") setFilter(false);
else {
setFilter(true);
setSearch(event.target.value);
}
};
const countriesList = filter
? countryList.filter((country) => {
return country.name.common.includes(search);
})
: null ;
return (
<div>
<Input onSearch={onSearch} search={search} />
{filter ? (
countriesList.length === 0 ? (
<h3>No match</h3>
) : countriesList.length > 10 ? (
<h3>Too many matches, specify another filter...</h3>
) : countriesList.length < 10 && countriesList.length > 1 ? (
countriesList.map((country, i) => (
<Country name={country.name.common} key={i} num={false} />
))
) : (
<Country name={countriesList[0].name.common} num={true} /> &&
console.log("common", countriesList)
)
) : (
<h3>Search for any country</h3>
)}
</div>
);
};
countrylist state must be an array.
Try using array in countyList as its undefined initially
const [countryList, setCountryList] = useState([]);
Also you seems to be you accessing filter (a state value directly while component initilize). Please try replacing with below code. Let me know if issue persists, should be a very simple fix
const [countriesList, setCountriesList] = useState([]);
useEffect(()=>{
if(filter){
setCountriesList(
countryList.filter((country) => {
return country?.name?.common?.includes(search);
}))
}else{
setCountriesList(countryList);
}
},[filter])
enter image description here Its a program , which counts no. of characters on input string. everything is coming correct on console, but i am not able to get it on UI. I have tried getting count.valueOf() , but it doesnot seems right.
import React, { useState } from 'react'
const CharCount = () => {
const [data, setData] = useState([])
const [print, setPrint] = useState(false)
const count= (value)=>{
const result = [...value].reduce((a, e) => { a[e] = a[e] ? a[e] + 1 : 1; return a }, [])
setPrint(false)
console.log(result)
return result
}
const clickHandler=()=>{
setPrint(true)
}
return (
<div>
<input type="text" value={data} onChange={ e=> setData(e.target.value)}/>
<div><button onClick={clickHandler}>CLICK!</button></div>
{
print?
<h1>
{count(data)}
</h1> : ""
}
</div>
)
}
export default CharCount
count(data) return an array , try this
print && (
<>
count(data).map(val => (<h1>{val}</h1> ))
</>
)
I use weather API for my application. The idea is to get the data from the API once as an array and pass it down for further processing. My App.js file looks like this:
import { useState, useEffect } from "react";
import Search from "./components/Search";
import axios from "axios";
function App() {
const [countries, setCountries] = useState([]);
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then((response) => {
setCountries(response.data);
});
}, []);
return (
<div>
<Search countriesList={countries} />
</div>
);
}
export default App;
The Search component includes a text input field, based on which the incoming array would be filtered and dynamically displayed. However, a function responsible for filtering is not invoked.
Here are the contents of the search component:
import { useState } from "react";
import Country from "./Country";
const Search = ({ countriesList }) => {
const [name, setName] = useState("");
console.log(countriesList);
console.log("countries received");
const filterCountries = (singleCountry, nameFilter) => {
console.log("hello");
console.log(singleCountry);
if (singleCountry.name.toLowerCase().includes(nameFilter.toLowerCase())) {
return singleCountry;
}
};
const countryRender = (showButtonCondition, showWeatherCondition) => {
return (
<div>
{countriesList
.filter((country) => filterCountries(country, name))
.map((filteredCountry) => (
<Country
key={filteredCountry.alpha3Code}
showButton={showButtonCondition}
showWeather={showWeatherCondition}
countryId={filteredCountry.alpha3Code}
countryName={filteredCountry.name}
countryCapital={filteredCountry.capital}
countryPopulation={filteredCountry.population}
countryLanguages={filteredCountry.languages}
countryFlag={filteredCountry.flag}
/>
))}
</div>
);
};
const nameChangeHandler = (event) => {
console.log(event.target.value);
setName(event.target.value);
};
return (
<div>
search: <input value={name} onChange={nameChangeHandler} />
<div>
{countriesList.length > 10 || countriesList.length === 0 ? (
<div>Too many countres, specify another filter</div>
) : (
<></>
)}
{countriesList.length === 1 ? countryRender(false, true) : <></>}
{countriesList.length > 1 && countriesList.length < 10 ? (
countryRender(true, false)
) : (
<></>
)}
</div>
</div>
);
};
export default Search;
I guess that the problem is the changing state of name (user input) that causes the whole Search component to re-render and get the full array anew, but how to overcome it? The React.memo() method doesn't seem to be applicable here, as the documentation states clearly that it shouldn't be used for preventing a component from re-rendering.
You are never actually calling countryRender(true, false). It only gets called when countriesList.length > 1 && countriesList.length < 10 but its length is 250.
When I click on a specific button I want to capture the {country} prop associated with it.
I tired the following
import React, { useState, useEffect } from 'react'
import axios from 'axios'
// ====================================================================[SEARCH-BAR]=======================================================
// search component
const SearchBar = (props) => {
// console.log(props);
const { searchString, searchOnChangeEventHandler } = props
return (
<>
<form>
<label>Search </label>
<input type='text' placeholder='type to search...' value={searchString} onChange={searchOnChangeEventHandler} />
</form>
</>
)
}
// ================================================================[COUNTRY_CARD]==========================================================
// countryCard component
const CountryCard = (props) => {
console.log(props);
return (
<div>
<p>countryName</p>
<p>capital</p>
<p>population</p>
<p>languages</p>
<ul>
<li>item</li>
<li>item</li>
</ul>
<p>image flag</p>
</div>
)
}
// ===================================================================[DISPLAY]===========================================================
// display component
const Display = (props) => {
const [showCountryCard, setShowCountryCard] = useState(false)
const [thisCountry, setThisCountry] = useState({})
// console.log(props);
const { countries, searchString } = props
// console.log(countries);
// eslint-disable-next-line eqeqeq
// searchString empty
if (searchString == false) {
return (
<>
<div>
<span>Type in SearchBar for a country...</span>
</div>
</>
)
}
// to count number of matches
const filteredResultsCount = countries.filter(country => country.name.toLowerCase().includes(searchString.toLowerCase())).length
// function to filterCountries
const filteredResults = (searchString, countries) => countries.filter(country => {
return country.name.toLowerCase().includes(searchString.toLowerCase())
})
// RENDER CONDITIONS
// searchString return <= 10 matches && >1 match
// event handler for show-btn
const showCardEventHandler = (event) => {
console.log(event.target.parentElement);
setShowCountryCard(!showCountryCard)
}
if (filteredResultsCount <= 10 && filteredResultsCount > 1) {
return (
<>
<ul>
{
filteredResults(searchString, countries).map(country =>
<li
key={country.numericCode}
country={country}
>
<span>{country.name}</span>
<button
value={showCountryCard}
onClick={showCardEventHandler}
>show</button>
</li>
)
}
</ul>
{
showCountryCard ? <p>show country card</p> : null
}
</>
)
}
// searchString returns >10 matches
if (filteredResultsCount > 10) {
return (
<span>{filteredResultsCount} matches!, please refine your search...</span>
)
}
// searchString returns ===1 match
if (filteredResultsCount === 1) {
return (
<>
{
filteredResults(searchString, countries).map(country => <CountryCard key={country.numericCode} country={country} />)
}
</>
)
}
// invalid searchString
if (filteredResultsCount === 0) {
return (
<span><strong>{filteredResultsCount} matches!</strong> please refine your search...</span>
)
}
}
// ===================================================================[APP]==============================================================
// app component
const App = () => {
// to store countries
const [countries, setCountries] = useState([])
// to fetch data from
const url = 'https://restcountries.eu/rest/v2/all'
useEffect(() => {
// console.log('effect');
axios
.get(url)
.then(response => {
// console.log('promise fulfilled');
const countries = response.data
// array of objects
setCountries(countries)
})
}, [])
// console.log('countries', countries.length);
// console.log(countries);
// to store search string
const [searchString, setSearchString] = useState('')
// event handler search input
const searchOnChangeEventHandler = (event) => setSearchString(event.target.value)
return (
<>
<h1>Countries Data</h1>
<SearchBar searchString={searchString} searchOnChangeEventHandler={searchOnChangeEventHandler} />
<br />
<Display countries={countries} searchString={searchString} />
</>
)
}
export default App
Please take a look at <Display/> component and in particular I'm trying to work on this part
const showCardEventHandler = (event) => {
console.log(event.target.parentElement);
setShowCountryCard(!showCountryCard)
}
if (filteredResultsCount <= 10 && filteredResultsCount > 1) {
return (
<>
<ul>
{
filteredResults(searchString, countries).map(country =>
<li
key={country.numericCode}
country={country}
>
<span>{country.name}</span>
<button
value={showCountryCard}
onClick={showCardEventHandler}
>show</button>
</li>
)
}
</ul>
{
showCountryCard ? <p>show country card</p> : null
}
</>
)
}
I want to be able to render a list of countries if they are more than 10 and allow a user to click on a specific country, which then will be used to render the <CountryCard/> component.
If there is only 1 matching value from search then I will directly display the country card component. The second functionality works.
After the following refactor the first functionality works, but Ima little confused as to why so I'm adding on to the post. This is the component being rendered and now I'm passing country prop onClick, like so
if (filteredResultsCount <= 10 && filteredResultsCount > 1) {
return (
<>
<ul>
{filteredResults(searchString, countries).map((country) => (
<li key={country.numericCode} country={country}>
<span>{country.name}</span>
<button
value={showCountryCard}
onClick={() => toggleCardEventHandler(country)}>
{showCountryCard ? 'hide' : 'show'}
</button>
</li>
))}
</ul>
{showCountryCard ? <CountryCard country={country} /> : null}
</>
);
}
The event handler is as follows
const toggleCardEventHandler = (country) => {
// console.log(country);
setShowCountryCard(!showCountryCard);
setCountry(country)
};
This works properly.
My question is, when I change the eventHandler onClick={toggleCardEventHandler(country)} it breaks, but shouldnt it be accessible through closure?
Also, if I change the code to this
onClick={() => {
toggleCardEventHandler()
setCountry(country)
}}
The code works the way I want but which is a better way to pass the value to the toggleCardEventHandler() and set the country there or to do it like this?
As I understand it you want to pass the country.name to your showCardEventHandler.
Update showCardEventHandler so it takes the event and the country name:
const showCardEventHandler = (event, countryName) => {
console.log(countryName);
setShowCountryCard(!showCountryCard)
}
Now pass the countryname to the function:
<li
key={country.numericCode}
country={country}
>
<span>{country.name}</span>
<button
value={showCountryCard}
onClick={e => showCardEventHandler(e, country.name)}
>show</button>
</li>
Since you are not using the event in showCardEventHandler you can remove it from the signature
const showCardEventHandler = (countryName) => {}
and call it with onClick={() => showCardEventHandler(country.name)}
I'm doing this fullstack course to learn about web dev: https://fullstackopen.com/en/part2/getting_data_from_server
And I have a problem with section 2.13*.
I am able to display a list of the countries after filtering with the button. Pressing the button returns the correct values from the countries arrays as seen with the console.log(country), but it doesn't to the screen.
My guess is that I can't return a div item within another item, but I am pretty sure that works in normal cases, so the fact that I'm returning the item to a different return statement might be the issue?
How can I fix this? I know my code is messy and a refactor might make things simpler, but it is currently beyond me right now since I find it easier to refactor working code.
In the DisplayCountries component, I've tried apply a map to countries that fit the filter input and prints it into a div item. Now when I add a button beside it, it displays correctly, but pressing it does not yield what I expect.
Is the correct approach here to use a useState with the button, so that each button click will rerender the screen? How would I go about doing this if so?
After pressing the button, the detailed information of the country should display such as in 2.12* from the linked website.
import { useState, useEffect } from 'react'
import axios from 'axios'
//feed array of countries
const printLanguages = (languages) => {
// console.log('map', languages.map(language => language.name))
return languages.map(language => <li key={language.name}>{language.name}</li>)
}
const displayCountryView = (country) => {
console.log(country)
return (
<div>
<h1>{country.name}</h1>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<h2>languages</h2>
<ul>
{printLanguages(country.languages)}
</ul>
<img src={country.flag} height="100" width="100"></img>
</div>
)
}
const DisplayCountries = ({ countries, searchValue }) => {
const displayFilter = filteredCountries(countries, searchValue)
// console.log('current search', searchValue)
if (displayFilter.length >= 10) {
return <p>Too many matches, specify another filter</p>
} else if (isFiltered(searchValue)) {
if (displayFilter.length > 1 && displayFilter.length < 10) {
console.log('new level')
return displayFilter.map(country => <div key={country.name}>{country.name}{showButton(country)}</div>)
} else if (displayFilter.length === 1) {
// console.log('suh')
// return displayFilter.map(country => <p key={country.name}>{country.name}</p>)
const country = displayFilter
return displayCountryView(country[0])
// console.log(country)
// console.log('country.name', country[0])
// console.log(country[0].languages)
// console.log(printLanguages(country[0].languages))
// return (
// <div>
// <h1>{country[0].name}</h1>
// <p>capital {country[0].capital}</p>
// <p>population {country[0].population}</p>
// <h2>languages</h2>
// <ul>
// {printLanguages(country[0].languages)}
// </ul>
// <img src={country[0].flag} height="100" width="100"></img>
// </div>
// )
}
} else {
return <p>empty</p>
}
}
const showButton = (country) => {
return <button type="button" onClick={() => displayCountryView(country)}>show</button>
}
const filteredCountries = (countries, searchValue) => {
const showCountries = (!isFiltered(searchValue))
? [{ name: "hi" }]
: countries.filter(country => country.name.toLowerCase().includes(searchValue.toLowerCase()))
// const countryMap = countries.map(country => country.name.toLowerCase())
// console.log(countryMap)
// return countryMap
return showCountries
}
function isFiltered(value) {
if (value === '') {
return false
} else {
return true
}
}
const Filter = ({ search, onChange }) => {
return (
<form >
<div>
find countries <input value={search} onChange={onChange} />
</div>
</form>
)
}
const App = () => {
const [countries, setCountries] = useState([])
const [search, setNewSearch] = useState('')
const [showCountry, setShowCountry] = useState('false')
useEffect(() => {
// console.log('effect')
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
// console.log('promise fulfilled')
setCountries(response.data)
})
}, [])
// const countryNames = countries.map(country => country.name)
// console.log('name', countryNames)
const handleSearchChange = (event) => {
setNewSearch(event.target.value)
}
// const fil = countries.filter(country => country.name==='Afg')
// console.log(countries[0])
// console.log('filtered:',fil)
// console.log(countries[0])
// console.log('render', countries.length, 'persons')
return (
<div>
<Filter search={search} onChange={handleSearchChange} />
<form>
<div>
<DisplayCountries countries={countries} searchValue={search} />
</div>
</form>
</div>
)
}
export default App;