JackJack
JackJack

Reputation: 193

React button to show an element

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

Upvotes: 0

Views: 130

Answers (2)

Han
Han

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

buzatto
buzatto

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

Related Questions