Aaron Yodaiken Aaron Yodaiken - 5 months ago 51
Javascript Question

Bubbling componentWillUpdate and componentDidUpdate

I know this is not idiomatic React, but I'm working on a React component (an inverse scrollview) which needs to get notified of downstream virtual DOM changes both before they are rendered and after they are rendered.

This is a little hard to explain so I made a JSFiddle with a starting point: https://jsfiddle.net/7hwtxdap/2/

My goal is to have the log look like:

Begin log

render

rendered small

rendered small

rendered small

before update

rendered big

after update

before update

rendered big

after update

before update

rendered big

after update


I'm aware that React sometimes batches DOM changes and that's fine (i.e. the log events could be
before update; rendered big; rendered big; after update; ...
) -- important part is that I am actually notified before and after the DOM changes.




I can manually approximate the behavior by specifying callbacks, as done here: https://jsfiddle.net/7hwtxdap/4/. However, this approach does not scale—I need, for example, to have events bubble from descendents rather than children; and would rather not have to add these kind of event handlers to every component I use.




Use case:

I'm working on making an "inverted scrolling component" for messages (where, when new elements are added or existing elements change size, the basis for scroll change is from the bottom of the content rather than the top ala every messaging app) which has dynamically modified children (similar to the
<MyElement />
class but with variable height, data from ajax, etc). These are not getting data from a Flux-like state store but rather using a pub-sub data sync model (approximated pretty well with the setTimeout()).

To make inverse scrolling work, you need to do something like this:

anyChildrenOfComponentWillUpdate() {
let m = this.refs.x;
this.scrollBottom = m.scrollHeight - m.scrollTop - m.offsetHeight;
}
anyChildrenOfComponentDidUpdate() {
let m = this.refs.x;
m.scrollTop = m.scrollHeight - m.offsetHeight - this.scrollBottom;
}


Importantly to this design, I'd like to be able to wrap the element around other elements that are not "inverted scrolling aware" (i.e. do not have to have special code to notify the inverted scrolling handler that they have changed).

Answer

It's hard to know what the best solution is to your problem without knowing why you need this functionality. However, I'll address this specific point:

I need, for example, to have events bubble from descendents rather than children; and would rather not have to add these kind of event handlers to every component I use.

I would suggest two changes to your solution for this. The first is to consider using context, but make sure to read the warnings before deciding to do so. This will allow you to define your callbacks in a single component and have them instantly available in all descendants.

The second change would be to move all your logging logic into a separate component, and have other components inherit it as needed. This component would look something like:

class LoggingComponent extends React.Component {
  componentWillUpdate() {
    if (this.context.onWillUpdate) {
        this.context.onWillUpdate();
    }
  }
  componentDidUpdate() {
    if (this.context.onDidUpdate) {
        this.context.onDidUpdate();
    }
  }
}

LoggingComponent.contextTypes = {
    onWillUpdate: React.PropTypes.func,
    onDidUpdate: React.PropTypes.func
};

This way, you can easily add this functionality to new components without duplicating the logic. I've updated your jsfiddle with a working demo.

Comments