Reputation: 923
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
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
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
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
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