3

I'm making a small Video component in React (for you guessed it, playing videos) and I want to embed that component inside a parent component and then be able to call a play method on the video component.

My video component looks like:

import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
const { string, func } = PropTypes;

export default class Video extends Component {

  static propTypes = {
    source: string.isRequired,
    type: string.isRequired,
    className: string
  };

  play = () => {

  };

  render = () => {
    const { className } = this.props;
    return (
      <video className={ className } width="0" height="0" preload="metadata">
        <source src={ this.props.source } type={ this.type } />
        Your browser does not support the video tag.
      </video>
    );
  };
}

It's really simple, nothing fancy going on here.

Now in the parent component, lets call it Page:

export default class Page extends Component {
    video = (
        <Video source="some_url" type="video/mp4" />
    );

    render = () => {
        <div onClick={ this.video.play } />
    }
}

However if I log .play it's undefined.

Next I tried declaring play as a prop in Video and putting a default prop like:

static defaultProps = {
    play: () => {
        const node = ReactDOM.findDOMNode(this);
    }
}

But in this context, this in undefined.

What is the proper way to expose a function on a React ES6 class so that it can be called by external components? Should I attach something to Video.prototype?

0

2 Answers 2

6

The correct way to call an instance method of a child component is to not do it. :-)

There are many resources here that talk about why, but to summarize: it creates an unclear data flow, it couples components together which decreases separation of concerns, and it is harder to test.

The best way to do what you want is to use an external service (e.g. event emitter) to manage the state. In Flux, these would be "stores". The Video component would trigger actions based on its current state (e.g. PLAYBACK_STARTED), which would in turn update the store. The Page component can fire a START_PLAYBACK action, which would also update the store. Both components listen for changes in the store's state, and respond accordingly. E.g.:

Page -> START_PLAYBACK -> Video (play) -> PLAYBACK_STARTED -> Page (update ui)

Flux is not a requirement here (e.g. you could use Redux or nothing at all). What's important here is a clear, unidirectional data flow.

Sign up to request clarification or add additional context in comments.

4 Comments

Yeah I'm using Redux along with React-Redux to connect the components. So the best way to do it would really be to connect the container component that holds the video to the redux store and when I want to play a video, just dispatch an action. And a separate action for pausing, etc.
In my opinion, that would be the best way. It seems a little cumbersome (and for a tiny app, it might be) but when your app scales a bit larger, it will be much cleaner. For example, perhaps you'd later want to automatically pause a video in response to a somewhat unrelated user action (e.g. opening a menu). You could pass the method up through the refs, but that's messy and unclear. It would be better to dispatch an action - set it and forget it.
Hmmm interesting, I do like that flow much better. The video component would then just call functions on its props that are defined by its parent components which can then do whatever they want such as perform a dispatch. And it wouldn't be hard to dispatch an action only to play a certain video. You could in theory store the video source as currentVideosPlaying in the redux store and then match them up in connected components.
And I definitely think that's the best way. I don't mind using the ref version suggested in the other answer since it certainly works fine but it's a messy architecture for large apps.
2

You can use refs for passing a method from a child to its parent.

export default class Page extends Component {
    video = (
        <Video source="some_url" ref="video" type="video/mp4" />
    );

    render = () => {
        <div onClick={() => this.refs.video.play()} />
    }
}

From Expose Component Functions

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.