Reputation: 193
I'm pulling countries from the Restcountries API and if the current state of the array has more than one or less than or equal to ten countries, I want to list the country names along with a 'show' button next to each one. The show button should display what's in the return (render) of my Country
function. In the App
function, I wrote a handler for the button named handleViewButton
. I'm confused on how to filter the element in the Countries
function in the else
conditional statement in order to display the Country
. I tried passing handleViewButton
to the Button
function, but I get an error 'Uncaught TypeError: newSearch.toLowerCase is not a function'. I really just want to fire the Country
function to display the country button that was pressed.
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import axios from 'axios';
const Country = ({country}) => {
return (
<>
<h2>{country.name}</h2>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<br/>
<h3>languages</h3>
{country.languages.map(language => <li key={language.name}>{language.name}</li>)}
<br/>
<img src={country.flag} alt="country flag" style={{ width: '250px'}}/>
</>
);
}
const Countries = ({countries, handleViewButton}) => {
const countriesLen = countries.length;
console.log(countriesLen)
if (countriesLen === 0) {
return (
<p>Please try another search...</p>
);
} else if (countriesLen === 1) {
return (
<ul>
{countries.map((country, i) => <Country key={i} countriesLen={countriesLen} country={country}/>)}
</ul>
);
} else if (countriesLen > 10) {
return (
<div>
<p>Too many matches, specify another filter</p>
</div>
);
} else {
return (
<ul>
{countries.map((country, i) => <li key={i}>{country.name}<Button handleViewButton={handleViewButton}/></li>)}
</ul>
)
};
};
const Button = ({handleViewButton}) => {
return (
<button onClick={handleViewButton}>Show</button>
);
};
const Input = ({newSearch, handleSearch}) => {
return (
<div>
find countries <input value={newSearch} onChange={handleSearch}/>
</div>
);
};
function App() {
const [countries, setCountries] = useState([]);
const [newSearch, setNewSearch] = useState('');
const handleSearch = (event) => {
const search = event.target.value;
setNewSearch(search);
};
const handleViewButton = (event) => {
const search = event.target.value;
setNewSearch(countries.filter(country => country === search));
};
const showCountrySearch = newSearch
? countries.filter(country => country.name.toLowerCase().includes(newSearch.toLowerCase()))
: countries;
useEffect(() => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(res => {
setCountries(res.data);
console.log('Countries array loaded');
})
.catch(error => {
console.log('Error: ', error);
})
}, []);
return (
<div>
<Input newSearch={newSearch} handleSearch={handleSearch}/>
<Countries countries={showCountrySearch} handleViewButton={handleViewButton}/>
</div>
);
};
export default App;
Upvotes: 0
Views: 130
Reputation: 758
I can only help to point out some guidelines.
First: The button does not have value attribute. Hence what you will get from event.target.value is always blank.
const Button = ({handleViewButton}) => {
return (
<button onClick={handleViewButton}>Show</button>
);};
First->Suggestion: Add value to the button, of course you need to pass the value in.
const Button = ({handleViewButton, value}) => {
return (
<button onClick={handleViewButton} value={value}>Show</button>
);};
Second: To your problem 'Uncaught TypeError: newSearch.toLowerCase is not a function'. Filter always returns an array, not a single value. if you do with console or some sandbox [1,2,3].filter(x=>x===2) you will get [2] not 2.
const handleViewButton = (event) => {
const search = event.target.value;
setNewSearch(countries.filter(country => country === search));
};
Second->Suggestion: To change it to get the first element in array, since country(logically) is unique.
const result = countries.filter(country => country === search)
setNewSearch(result.length>0?result[0]:"");
A better approach for array is find, which always return first result and as a value. E.g. [1,2,2,3].find(x=>x===2) you will get 2 not [2,2] or [2].
countries.find(country => country === search)
Upvotes: 0
Reputation: 10382
you can use a displayCountry
to handle the country that should be displayed. Most often you would use an id, but I'm using here country.name
since it should be unique.
Then you would use matchedCountry
to find against your list of countries.
After that, a onHandleSelectCountry
to select a given country. if it's already selected then you could set to null
to unselect.
Finally, you would render conditionally your matchedCountry
:
const Countries = ({countries}) => {
const [displayCountry, setDisplayCountry] = useState(null);
const countriesLen = countries.length;
const matchedCountry = countries.find(({ name }) => name === displayCountry);
const onHandleSelectCountry = (country) => {
setDisplayCountry(selected => {
return selected !== country.name ? country.name : null
})
}
if (countriesLen === 0) {
return (
<p>Please try another search...</p>
);
} else if (countriesLen === 1) {
return (
<ul>
{countries.map((country, i) => <Country key={i} countriesLen={countriesLen} country={country}/>)}
</ul>
);
} else if (countriesLen > 10) {
return (
<div>
<p>Too many matches, specify another filter</p>
</div>
);
} else {
return (
<>
<ul>
{countries.map((country, i) => <li key={i}>{country.name}<Button handleViewButton={() => onHandleSelectCountry(country)}/></li>)}
</ul>
{ matchedCountry && <Country countriesLen={countriesLen} country={matchedCountry}/> }
</>
)
};
};
Upvotes: 1