Search code examples
javascriptreactjsevent-handlingreact-props

Can I retrieve a attribute value from a parent element in ReactJS?


enter image description here

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?


Solution

  • 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)}