foakesm
foakesm

Reputation: 923

React.JS component's onClick function, how to be more specific with its result

Currently I have a React component that takes data on music artists from an API and creates a list of media objects showing details of that. The media objects have an onClick function so that when a box is clicked it displays a Child component with more specific details, that all works,

HOWEVER when one object in the list is clicked they ALL display the child components beneath them. How would I go about narrowing this down so that each object in the list can be individually clicked without affecting the others?

Here's my code:

import React, { Component } from 'react';
import axios from 'axios';
import ArtistChild from './track-child';

class ArtistListEntry extends Component {
  constructor(props) {
   super(props);

   this.state = {
     error: null,
     isHidden: true,
     artist: []
   };
 }

 toggleHidden () {
    this.setState({
      isHidden: !this.state.isHidden
    })
  }

 componentDidMount() {
   this.ArtistListEntry();
 }

 ArtistListEntry() {
   axios.get('https://api-v2.hearthis.at/feed/?type=popular&page=1&count=20')
     .then(
       ({ data }) => {
         this.setState({
           artist: data
         })
       })
       .catch((error) => {
        if (error.response) {
            console.log(error.response.status);
            console.log(error.response.headers);
        } else if (error.request) {
            console.log(error.request);
        } else {
            console.log('Error', error.message);
        }
        console.log(error.config);
    });
 }

 render() {
   let artists = this.state.artist.map((item) => (
     <div>
       <div className='media media-top' key={ item.user.id } onClick={this.toggleHidden.bind(this)}>
        <div className='media-left media-middle'>
          <img src={ item.user.avatar_url } className='media-object artist-image' />
        </div>
        <div className='media-body'>
          <ul className='artist-info-list'>
            <li className='media-heading'>{ item.user.username }</li>
            <li className='media-genre'>{ item.genre }</li>
            <a className='media-release' href={ item.permalink_url }>Visit Artist Page</a>
          </ul>
        </div>
      </div>
      {!this.state.isHidden && <ArtistChild />}
    </div>
  ));

   return (
     <div id="layout-content" className="layout-content-wrapper">
       <div className="panel-list">
         { artists }
       </div>
     </div>
   );
 }
}

export default ArtistListEntry;

Thanks in advance!

Upvotes: 1

Views: 132

Answers (4)

Shubham Khatri
Shubham Khatri

Reputation: 281656

You are maintaining a single state to toggle the view which is the same for all list item, So either you need to maintain a list for hidden items or the better way is to store which item needs to be shown(if only one needs to be displayed at once)

import React, { Component } from 'react';
import axios from 'axios';
import ArtistChild from './track-child';

class ArtistListEntry extends Component {
  constructor(props) {
   super(props);

   this.state = {
     error: null,
     isVisible: '',
     artist: []
   };
 }

 toggleHidden (id) {
    this.setState({
      isVisible: id
    })
  }

 componentDidMount() {
   this.ArtistListEntry();
 }

 ArtistListEntry() {
   axios.get('https://api-v2.hearthis.at/feed/?type=popular&page=1&count=20')
     .then(
       ({ data }) => {
         this.setState({
           artist: data
         })
       })
       .catch((error) => {
        if (error.response) {
            console.log(error.response.status);
            console.log(error.response.headers);
        } else if (error.request) {
            console.log(error.request);
        } else {
            console.log('Error', error.message);
        }
        console.log(error.config);
    });
 }

 render() {
   let artists = this.state.artist.map((item) => (
     <div>
       <div className='media media-top' key={ item.user.id } onClick={this.toggleHidden.bind(this, item.id)}>
        <div className='media-left media-middle'>
          <img src={ item.user.avatar_url } className='media-object artist-image' />
        </div>
        <div className='media-body'>
          <ul className='artist-info-list'>
            <li className='media-heading'>{ item.user.username }</li>
            <li className='media-genre'>{ item.genre }</li>
            <a className='media-release' href={ item.permalink_url }>Visit Artist Page</a>
          </ul>
        </div>
      </div>
      {this.state.isVisible === item.id  && <ArtistChild />}
    </div>
  ));

   return (
     <div id="layout-content" className="layout-content-wrapper">
       <div className="panel-list">
         { artists }
       </div>
     </div>
   );
 }
}

export default ArtistListEntry; 

In case multiple items need to be toggled you need to maintain the open state for all the items

import React, { Component } from 'react';
import axios from 'axios';
import ArtistChild from './track-child';

class ArtistListEntry extends Component {
  constructor(props) {
   super(props);

   this.state = {
     error: null,
     isHidden: {},
     artist: []
   };
 }

 toggleHidden (itemId) {
    this.setState(prevState => ({
      isHidden: {[itemId]: prevState.isHidden[itemId] ? !prevState.isHidden[itemId] : false
    }))
  }

 componentDidMount() {
   this.ArtistListEntry();
 }

 ArtistListEntry() {
   axios.get('https://api-v2.hearthis.at/feed/?type=popular&page=1&count=20')
     .then(
       ({ data }) => {
         this.setState({
           artist: data
         })
       })
       .catch((error) => {
        if (error.response) {
            console.log(error.response.status);
            console.log(error.response.headers);
        } else if (error.request) {
            console.log(error.request);
        } else {
            console.log('Error', error.message);
        }
        console.log(error.config);
    });
 }

 render() {
   let artists = this.state.artist.map((item) => (
     <div>
       <div className='media media-top' key={ item.user.id } onClick={this.toggleHidden.bind(this)}>
        <div className='media-left media-middle'>
          <img src={ item.user.avatar_url } className='media-object artist-image' />
        </div>
        <div className='media-body'>
          <ul className='artist-info-list'>
            <li className='media-heading'>{ item.user.username }</li>
            <li className='media-genre'>{ item.genre }</li>
            <a className='media-release' href={ item.permalink_url }>Visit Artist Page</a>
          </ul>
        </div>
      </div>
      {!this.state.isHidden[item.id]  && <ArtistChild />}
    </div>
  ));

   return (
     <div id="layout-content" className="layout-content-wrapper">
       <div className="panel-list">
         { artists }
       </div>
     </div>
   );
 }
}

export default ArtistListEntry;

Upvotes: 1

Birbal Singh
Birbal Singh

Reputation: 1072

Your code be look like

toggleHidden (item) {
    item.isHidden  = (!item.isHidden) || true;
    this.forceUpdate();
  }

render code

let artists = this.state.artist.map((item) => (
     <div>
       <div className='media media-top' key={ item.user.id } onClick={this.toggleHidden.bind(this, item)}>
        <div className='media-left media-middle'>
          <img src={ item.user.avatar_url } className='media-object artist-image' />
        </div>
        <div className='media-body'>
          <ul className='artist-info-list'>
            <li className='media-heading'>{ item.user.username }</li>
            <li className='media-genre'>{ item.genre }</li>
            <a className='media-release' href={ item.permalink_url }>Visit Artist Page</a>
          </ul>
        </div>
      </div>
      {item.isHidden && <ArtistChild />}
    </div>
  ));


//  you need to check {item.isHidden && <ArtistChild />}

Upvotes: 0

Mor Shemesh
Mor Shemesh

Reputation: 2889

The problem is you have one state property isHidden which controls the visibility for all artists, instead a state property for each artist, or better yet, an activeArtistId state property which should state which artist to display.

Should look something like:

// ...

class ArtistListEntry extends Component {
  constructor(props) {
   super(props);

   this.state = {
     error: null,
     activeArtistId: null,
     artist: []
   };
 }

 toggleHidden (artistId) {
    this.setState({
      activeArtistId: artistId
    })
  }

 // ...

 ArtistListEntry() {
   axios.get('...')
     .then(
       ({ data }) => {
         this.setState({
           artist: data,
           activeArtistId: null
         })
       })

       // ...
 }

 render() {
   let artists = this.state.artist.map((item) => (
     <div>
       <div className='media media-top' key={ item.user.id } onClick={this.toggleHidden.bind(this, item.user.id)}>
        {/*...*/}
      </div>
      {this.state.activeArtistId === item.user.id && 
       <ArtistChild artistId={item.user.id} />}
    </div>
  ));

   // ...
 }
}

Upvotes: 1

Keith Rousseau
Keith Rousseau

Reputation: 4475

Update your onClick to take the id of the hidden object and your state would do the same:

this.state = {
     error: null,
     hidden: {},
     artist: []
};

Then your onClick would be this:

onClick={() => {
  this.setState(prevState => {
    return { hidden: { ...prevState, [item.user.id]: !prevState.hidden[item.user.id] } };
  });
}}

You would then check to see if state.hidden[item.user.id] is true

Upvotes: 0

Related Questions