Функция onClick компонента React.JS, как быть более конкретным с его результатом

В настоящее время у меня есть компонент React, который берет данные о музыкальных исполнителях из API и создает список объектов мультимедиа, показывающих детали этого. Мультимедийные объекты имеют функцию onClick, поэтому при щелчке по блоку отображается дочерний компонент с более конкретными сведениями, что все работает,

ОДНАКО при нажатии на один объект в списке они ВСЕ отображают дочерние компоненты под ними. Как мне сузить это, чтобы каждый объект в списке можно было нажимать по отдельности, не затрагивая другие?

Вот мой код:

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;

Заранее спасибо!

4 ответа

Решение

Вы поддерживаете одно состояние для переключения вида, который является одинаковым для всех элементов списка, поэтому либо вам нужно поддерживать список для скрытых элементов, либо лучший способ - сохранить, какой элемент должен отображаться (если требуется только один отображается сразу)

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; 

В случае необходимости переключения нескольких элементов необходимо поддерживать открытое состояние для всех элементов.

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;

Проблема в том, что у вас одна государственная собственность isHidden который контролирует видимость для всех художников, вместо этого государственное свойство для каждого художника, или, что еще лучше, activeArtistId государственная собственность, которая должна указать, какого исполнителя отображать.

Должно выглядеть примерно так:

// ...

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>
  ));

   // ...
 }
}

Обновите свой onClick, чтобы получить идентификатор скрытого объекта, и ваше состояние будет делать то же самое:

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

Тогда ваш onClick будет таким:

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

Затем вы проверите, является ли state.hidden[item.user.id] истинным

Ваш код будет похож

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

код рендеринга

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 />}
Другие вопросы по тегам