0

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;

enter image description here

2 Answers 2

1

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}/> }
          </>
        )
    };
};
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, buzatto, that worked. I have one question. Why did you put name in curly braces in the find method for matchedCountry? Ex. countries.find(({ name }) => name === displayCountry); Does that look for name directly in the countries object?
In some sense yes. Since country it is an object, you can use this syntax to extract its properties. You can extract multiples properties, this way writing a cleaner code. You can look more info about searching for "destructuring javascript Es6"
If you look at your code you do the same thing I do at your components where you destruct props to extract country , that's an object.
0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.