1

Let's say I have the following:

<Group>
  <Item mediaUrl="http://some/media/file.mp3" />
  <Item mediaUrl="http://another/media/file.mp3" />
</Group>

When the first file is done playing, I'm able to notify Group about it via a method passed via props:

// Group.js
const items = React.Children.map(this.props.children, (child, i) => {
  // ...
  return React.cloneElement(child, { onEnd: this.handleEnd });
});

What I can't figure out is, how do I make Group fire off a method within Item, without using something like Redux? (I'm trying to keep these components as simple and pure as possible)

ffxsam
  • 26,428
  • 32
  • 94
  • 144

3 Answers3

2

You cannot and should not. But in few cases, like setting focus or triggering something that does not really change state it can be needed. The key of this problem is that DOM is not created yet. There is an example in react doc.

render: function() {
  return <TextInput ref={(c) => this._input = c} />;
},
componentDidMount: function() {
  this._input.focus();
},

So here you set ref attribute and then after dom is mounted you set focus. In other cases I would recommend to do everything using passing properties from parent to children components.

More things to help:

Community
  • 1
  • 1
Ross Khanas
  • 1,491
  • 2
  • 15
  • 23
0

Figured out a solution that doesn't involve hacks or refs, and thought I'd share.

My Group component has the following methods:

  componentDidMount() {
    this.setState({
      playlist: React.Children.map(this.props.children, t => t.props.id),
    });
  }

  render() {
    if (!this.props.children) {
      console.error('Warning: TrackGroup must contain audio tracks');
      return null;
    }

    const tracks = React.Children.map(this.props.children, (child, i) => {
      return React.cloneElement(child, {
        onTrackEnd: this.trackEnded,
        playNext: this.state.playNext,
      });
    });

    return <div>{tracks}</div>
  }

And when each Item's (track) componentWillReceiveProps fires, I check nextProps.playNext to see if it matches this.props.id. If it does, the audio calls this.clickedPlay() (as if the user had clicked the play button). Works like a charm.

ffxsam
  • 26,428
  • 32
  • 94
  • 144
0

I assume here that you are trying to play the next song? The way I might try to solve this problem is by passing down another prop, probably a boolean, that determines whether or not the song is played on render. The parent would keep track of which song played last, is playing, or will play next within its state. When your song-end event fires, the parent will call setState and update each of those state values and re-render itself and its children accordingly.

Component(s):

class Group extends React.Component {
  constructor(props) {
    super(props)
  }
  state = {
    // Initial state. Be careful pressing the back button on the first song :p (Obviously improve this)
    previous: -1,
    current: 0,
    next: 1
  }
  handleEnd = () => {
    // Perform all sorts of condition checks for first song, last song, etc. and code accordingly. This is simplistic, proceeding in linear fashion. You can extrapolate this into other methods for selection of any song, back or forward button, etc.
    if (...) {
      this.setstate({
        previous: previous += 1,
        current: current += 1,
        next: next +=1
      });
    }
  }
  render () {
    let songs = this.props.songs;

    let songList = songs.map((song) => {
      if (song.index === this.state.current) {
        return (
          <Item playing=1 onEnd={this.handleEnd} mediaUrl={song.url} />
        );
      }
      else {
        return (
          <Item playing=0 onEnd={this.handleEnd} mediaUrl={song.url} />
        );
      };
    }

    return (
      <div>
        {songList}
      </div>
    );
  }
}

class Item extends React.Component {
  constructor(props) {
    super(props)
  }
  componentDidMount = () => {
    if (this.props.playing === 1) {
      this.play();
    };
  }
  play = () => {
    // Play the music from the url or whatever.
  }
  render () {
    <div>
      // Some graphical representation of song.
    </div>
  }
}

Creating a new playlist component:

someSongs = [...];
ReactDOM.render(
  <Group songs={someSongs} />,
  document.getElementById('...')
);
Ezra Chang
  • 1,268
  • 9
  • 12