Preact: External API methods for a component

Created on 17 May 2018  路  4Comments  路  Source: preactjs/preact

First up, let me say I'm an old school Javascript person; Preact (and React) is new to me, so if I'm talking rubbish, feel free to shoot me down :).

Basically what I'm looking to be able to do is define methods on a class I am creating (extending preact.Component) and then use those methods. Defining them is easy - using them, not so much. In plain Javascript I'd just do new ClassName(), but in Preact you use the render function, which returns a DOM node.

I found #955 which relates to this and using the solution there I can get my instance and call its methods:

    let instance;
    let ref = c => { instance = c; };
    preact.render(<Menu ref={ref} />, menu.parent[0], menu[0] );

    // Then sometime later - after a socket update with new menu items:
    instance.data( ... );

That feels really messy and doesn't feel like the right way of doing it with Preact. Its a lot of messing around just to get the class instance (rather than let instance = new Menu()).

Is there a better way of doing this? Should I in fact be passing the data in as a property to a new preact.render(<Menu...) call? Does that reuse the old instance (it looks like a factory method, so I'm guessing not).

I guess I'm just haven't a little trouble knowing what the right way to do it would be and meshing that with my old school way of thinking!

question

Most helpful comment

Hi @DataTables! While that's the best way to call methods on a component instance, it seems like what you want to do is re-render in response to new data. I'd recommend re-invoking render() for that:

let parent = menu.parent[0];  // I'm assuming this is coming from jQuery or something
let root = menu[0];

preact.render(<Menu data={{}} />, parent, root);

onSocketUpdate = newData => {
  preact.render(<Menu data={newData} />, parent, root);
};

This will update your tree of components (including Menu) in-place. If you want to Menu to detect when it gets a new data value, define a componentWillReceiveProps() method on that class:

class Menu extends preact.Component {
  componentWillReceiveProps(nextProps) {
    if (nextProps.data !== this.props.data) {
      // got new data
    }
  }
  render(props) {
    // do stuff with props.data here
  }
}

For what it's worth, it's often easier to "set up" things like socket connections and external data sources from within your root component (Menu) or a provider component:

// wrapper component that maintains the socket connection and renders its child with new data
class DataProvider {
  componentDidMount() {
    this.connection = new SocketConnection();
    this.connection.ondata = data => {
      this.setState({ data });
    };
  }
  componentWillUnmount() {
    this.connection.close();
  }
  render(props, state) {
    return props.children[0](state.data)
  }
}

// usage:

preact.render(
  <DataProvider>
    { data => <Menu data={data} /> }
  </DataProvider>,
  menu.parent[0],
  menu[0]
)

The above keeps things nicely in the component tree, so your setup code is left clean.

All 4 comments

Hi @DataTables! While that's the best way to call methods on a component instance, it seems like what you want to do is re-render in response to new data. I'd recommend re-invoking render() for that:

let parent = menu.parent[0];  // I'm assuming this is coming from jQuery or something
let root = menu[0];

preact.render(<Menu data={{}} />, parent, root);

onSocketUpdate = newData => {
  preact.render(<Menu data={newData} />, parent, root);
};

This will update your tree of components (including Menu) in-place. If you want to Menu to detect when it gets a new data value, define a componentWillReceiveProps() method on that class:

class Menu extends preact.Component {
  componentWillReceiveProps(nextProps) {
    if (nextProps.data !== this.props.data) {
      // got new data
    }
  }
  render(props) {
    // do stuff with props.data here
  }
}

For what it's worth, it's often easier to "set up" things like socket connections and external data sources from within your root component (Menu) or a provider component:

// wrapper component that maintains the socket connection and renders its child with new data
class DataProvider {
  componentDidMount() {
    this.connection = new SocketConnection();
    this.connection.ondata = data => {
      this.setState({ data });
    };
  }
  componentWillUnmount() {
    this.connection.close();
  }
  render(props, state) {
    return props.children[0](state.data)
  }
}

// usage:

preact.render(
  <DataProvider>
    { data => <Menu data={data} /> }
  </DataProvider>,
  menu.parent[0],
  menu[0]
)

The above keeps things nicely in the component tree, so your setup code is left clean.

That's super-helpful thanks! The socket is going to be shared, so I'll probably pass it in as a proper and let the component listen to the event it needs to.

That said, I'm a little confused about componentWillReceiveProps() and the first bit of code you posted:

preact.render(<Menu data={{}} />, parent, root);

onSocketUpdate = newData => {
  preact.render(<Menu data={newData} />, parent, root);
};

To my preact uneducated mind this will create two instances of the Menu component (one replacing the other, doing the DOM diffing stuff). But the componentWillReceiveProps() code suggests that actually there is something doing magic somewhere to see if there is already an instance, and then reuse that. Is that correct? Does it just use DOM elements to keep track of unique instances?

@DataTables yep, preact diffs directly against the DOM and not another vdom-tree like react does.

Super - thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jescalan picture jescalan  路  3Comments

nopantsmonkey picture nopantsmonkey  路  3Comments

Zashy picture Zashy  路  3Comments

rajaraodv picture rajaraodv  路  3Comments

kay-is picture kay-is  路  3Comments