2

I've created a project where I call an API to list all of the countries in the world and some facts about them. I have each country on a different card, and I want the card to say the country's name on the front and then flip to the back and show the country's continent and language when the user clicks the card. The problem is that when the user clicks the card, all of the cards flip. I realize now that I need to use id or something similar to target specific cards, but I can't figure out how.

What I've tried: There are various versions of this question on here and elsewhere, but often the code is much longer and it's harder to follow the advice, and some of those deal with changing the CSS or situations different from what I'm trying to do. I tried to create a state of 'clickedArray' to create an array that shows true of false for if any specific index is clicked, but I couldn't figure out where I could call a method that fills that array, and also I don't know if that's the correct strategy anyway.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [],
      loading: true,
      clicked: false
    }
  }
  
  componentDidMount() {
    fetch('https://restcountries.eu/rest/v2/all')
      .then(response => response.json())
      .then(json => this.setState({data: json}));
    this.setState({loading: false});
  }

  clickHappens = () => {
    this.setState({clicked: this.state.clicked ? false : true});
  }
  
  render() {
    return (
      <div className="container">
        {this.state.data?.length > 0 && this.state.data.map((item, id) => (
          <div className="box" key={id} onClick={this.clickHappens}>
            {this.state.clicked === false ? 
              <Countryname name={item["name"]}/>
              :
              <Countryinformation continent={item["subregion"]} language={item["languages"][0]["name"]} />
            }
          </div>
        ))}
      </div>
    )
  }
}

class Countryname extends React.Component {
  render() {
    return (
      <h1>{this.props.name}</h1>
    )
  }
}

class Countryinformation extends React.Component {
  render() {
    return (
      <>
      <p>{this.props.continent}</p>
      <p>{this.props.language}</p>
      </>
    )
  }
}

export default App;
0

1 Answer 1

2

This is because you are using a single state value to queue all the elements from if they are clicked or not.

If you want to toggle a single card at a time, use the index to match by.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [],
      loading: true,
      clicked: null // <-- start with null state
    }
  }
  
  componentDidMount() {
    fetch('https://restcountries.eu/rest/v2/all')
      .then(response => response.json())
      .then(data => this.setState({ data }))
      .finally(() => this.setState({ loading: false }));
  }

  clickHappens = (id) => () => {
    this.setState(prevState => ({
      clicked: prevState.clicked === id ? null : id, // <-- toggle back to null or to new id
    }));
  }
  
  render() {
    return (
      <div className="container">
        {this.state.data?.map((item, id) => (
          <div
            className="box"
            key={id}
            onClick={this.clickHappens(id)} // <-- pass id to toggle
          >
            {this.state.clicked === id ? // <-- check id match
              <Countryinformation
                continent={item["subregion"]}
                language={item["languages"][0]["name"]}
              />
              :
              <Countryname name={item["name"]}/>
            }
          </div>
        ))}
      </div>
    )
  }
}

If you want to toggle multiple then use a map object to store the ids.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [],
      loading: true,
      clicked: {} // <-- start with empty object
    }
  }
  
  componentDidMount() {
    fetch('https://restcountries.eu/rest/v2/all')
      .then(response => response.json())
      .then(data => this.setState({ data }))
      .finally(() => this.setState({ loading: false }));
  }

  clickHappens = (id) => () => {
    this.setState(prevState => ({
      clicked: {
        ...prevState.clicked,
        [id]: !prevState.clicked[id], // <-- toggle clicked boolean
      },
    }));
  }
  
  render() {
    return (
      <div className="container">
        {this.state.data?.map((item, id) => (
          <div
            className="box"
            key={id}
            onClick={this.clickHappens(id)} // <-- pass id to toggle
          >
            {this.state.clicked[id] ? // <-- check id match
              <Countryinformation
                continent={item["subregion"]}
                language={item["languages"][0]["name"]}
              />
              :
              <Countryname name={item["name"]}/>
            }
          </div>
        ))}
      </div>
    )
  }
}
Sign up to request clarification or add additional context in comments.

5 Comments

Very helpful, thank you. Can you or somebody explain why you added the extra () in clickHappens to make it clickHappens = (id) => () => et cetera? A quick search says that it's a curried function, but if whoever could explain this in the context of this question, it could help me and other people as well.
And also, regarding the comment before, is there a way to write that without using a curried function? In order to understand what the equivalent would be.
@phershbe Correct! It is a curried function. The benefit here is you can close over in callback scope the id and not need to use an anonymous callback function when attaching the handler. In other words, you can write onClick={this.clickHappens(id)} versus onClick={() => this.clickHappens(id)} where clickHappens would be a regular callback, i.e. const clickHappens = id => { ..... }. There's not much of a difference either way, so it mostly comes down to preference. (I'm a fan of writing JSX as succinctly as possible)
Yeah it was perfect. I'm relatively new here, so I didn't realize that I should accept the answer. It's done now! Explaining the curried function was great too because I know that I had write the function in the onClick in that specific way if it took any parameters, but I didn't understand why and didn't know that I could also use currying when writing the method instead.
Why is this not getting more likes. Thanks @DrewReese

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.