4

I have a parent Component [MainLayout] that has a child [ListItems] and that has multiple children [ListItem].

How can I get the value of the clicked child [ListItem] in the [MainLayout] component?

/* index.js */

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Link, IndexRoute } from 'react-router'
import ListItems from './components/listitems';

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

        this.state = {
            items: [],
            selectedItem: null
        };

        this.getTracks = this.getTracks.bind(this);

        this.listItemClicked = this.listItemClicked.bind(this);

        this.getTracks();

    }

    listItemClicked(item) {
        console.log(item);
    }

    getTracks() {

fetch('https://api.spotify.com/v1/search?q=newman&type=track&market=US')
                .then((response) => response.json())
                .then((responseJson) => {
                    this.setState({items: responseJson.tracks.items});
                    console.log(responseJson);
                    return responseJson;
                });
    }

    render() {
        return (
            <div>
                {this.props.children && React.cloneElement(this.props.children, {
                    items: this.state.items,
                    onListItemClicked: this.listItemClicked
                })}
            </div>
        );
    }
}

class App extends Component {
  constructor(props) {
    super(props);
  }

  render() {

    return (
        <div>
            <ListItems onListItemClick={this.props.onListItemClicked} items={this.props.items} />
        </div>
    );
  }

}


/* listitems.js */

import React, {Component} from 'react';
import ListItem from './listitem';

const ListItems = (props) => {

    const allitems = props.items.map((item) => {
        return (
            <ListItem onListItemClick={props.onListItemClick} item={item} key={item.id} />
        )
    });


        return (
            <ul className="list-group">
                {allitems}
            </ul>
        );


}
export default ListItems;

/* listitem.js */

import React, {Component} from 'react';

class ListItem extends Component {

    constructor (props) {
        super(props);
    }

    render() {
        return (
            <div className="">
                <h4 onClick={this.props.onListItemClick}>{this.props.item.album.artists['0'].name} - {this.props.item.name}</h4>
            </div>
        );
    }

}
export default ListItem;

Thanks for the answers!

dallion
  • 195
  • 5
  • 16

2 Answers2

1

The solution you want can be found here.

But I would suggest you using any separate architecture like Redux or Flux. I know you wanted an answer with less overhead, but believe me, using this is going to save you a lot of time and they handle each and every state effectively. I will talk about Redux as I have only worked on Redux.


How Redux solves you problem?

All the data you need are stored in Redux store which can be accessed, altered from any page. When ever you want alter any property, there is method available called dispatch() which helps you to do so. In order to access the values from store, you can use @connect() decorators.


To know more about dispatcher, see this link.

To know about @connect() decorator, see this link.

Difference between Redux and Flux can be found here and here.

Community
  • 1
  • 1
bharadhwaj
  • 2,059
  • 22
  • 35
0

in your listitem, you can call the onListItemClick and pass the item as parameter like onClick={() => { this.props.onListItemClick(this.props.item); }}

/* listitem.js */

import React, {Component} from 'react';

class ListItem extends Component {

    constructor (props) {
        super(props);
    }

    render() {
        return (
            <div className="">
                <h4 onClick={() => { this.props.onListItemClick(this.props.item); }}>{this.props.item.album.artists['0'].name} - {this.props.item.name}</h4>
            </div>
        );
    }

}
export default ListItem;
phoa
  • 251
  • 1
  • 6
  • I tried this before, the click event gets through to [MainLayout] only once on pageload, therefore it does NOT work on click on an individual ListItem – dallion Nov 23 '16 at 08:46
  • you may want to move the `this.getTracks();` to `componentDidMount` lifecycle since you're changing the state. don't do it in constructor. You can refer to [https://facebook.github.io/react/docs/react-component.html#componentdidmount](https://facebook.github.io/react/docs/react-component.html#componentdidmount) – phoa Nov 23 '16 at 08:57
  • If I console.log the this.state.items in the componentDidMount() method I get an empty array.. could you advise why? – dallion Nov 23 '16 at 10:11
  • That is correct. Your state has an empty items initially. It is only populated after the getTracks() successfully fetched the items from spotify. Fetch is asynchronous. ComponentDidMount calls getTracks() which then calls fetch, and continues. It does not wait for fetch to finish. Once fetch has the result then the state is updated and it triggers re-rendering process again with updated items. Hope it makes sense. – phoa Nov 23 '16 at 10:29
  • Yes, I understand the way the data fetching works, I would like to know how to prevent this from happening, ie. wait with the rendering until there is some real data. Also, the first onClick on the grandchild component returns a null, the second third and so on return real data. Why is that? – dallion Nov 23 '16 at 12:17
  • when items is empty you can render null in your MainLayout or render a div that says loading... or you can have a state, like isFirstTimeLoad that is true initially and set it to false regardless of fetch results and use it to conditionally render null or render the listItems. Not sure what you meant by first click.. is it when items still empty? or the first click of first item after items contains something? – phoa Nov 23 '16 at 12:47
  • the first click regardless whether the data has been fetched or not.. after a refresh I can leave the page to fetch the data it needs, save it to the state via setState() and then the first click on a ListItem returns null.. the second click returns real data as it should – dallion Nov 24 '16 at 15:13
  • Not sure what's going on there. What about `{this.props.item.name}` in the `

    `, is it shown correctly on that first click?

    – phoa Nov 25 '16 at 01:34
  • On the first click the whole data returned from the child is NULL – dallion Nov 25 '16 at 08:45