Fixing ghost panning – a reboot

In my last post about gesture decorators I came up with a rather crude solution to fix the ghost panning issue. It turns out that there is a much cleaner, much more elegant solution to this problem.

Why does ghost panning appear at all? It’s because our wrapping elements take up as much space as possible – due to flexbox. So what if we could force the wrappers to effectively display like an inline item, so that it would only take up as much space as necessary?

Enter alignSelf: 'flex-start' #

After attending React Europe and chatting with @vjeux it turns out we have a much cleaner method of getting our wrappers to behave properly.

By adding alignSelf: 'flex-start' to the wrapper’s style, we achieve exactly what is written above – an inline version of our element, which automatically resizes itself to its content’s size.

This enables us to remove the cruft which has been added with the last iteration and substitute it with a very tiny piece. But it does not only clean up our code, it allows to really compose our decorators together, as there is no need for any size updates to bubble up the tree. Our decorators just wrap themselves around the components as tight as possible.

Old vs new #

Here is a quick comparison of the changed parts for the panning decorator using the old and the new method:

Constructor #

before #

constructor(props, context) {
  super(props, context);

  this.lastX = 0;
  this.lastY = 0;

  this.state = {
    absoluteChangeX: 0,
    absoluteChangeY: 0,
    changeX: 0,
    changeY: 0,
    decoratedViewWidth: null,
    decoratedViewHeight: null
  };
}

after #

constructor(props, context) {
  super(props, context);

  this.lastX = 0;
  this.lastY = 0;

  this.state = {
    absoluteChangeX: 0,
    absoluteChangeY: 0,
    changeX: 0,
    changeY: 0
  };
}

componentDidMount #

before #

componentDidMount() {
  setTimeout(() => {
    UIManager.measure(React.findNodeHandle(this.refs.decorated), (x, y, w, h) => {
      this.setState({
        decoratedViewHeight: h,
        decoratedViewWidth: w
      });
    }, 0);
  });
}

after #

poof – gone!

render #

before #

render() {
  const {
    onPanBegin,
    onPan,
    onPanEnd,
    panningDecoratorStyle,
    ...props
  } = this.props;

  const state = {
    decoratedViewWidth: width,
    decoratedViewHeight: height,
    ...rest
  } = this.state;

  const style = {
    ...panningDecoratorStyle,
    width,
    height
  };

  return (
    <View {...this._panResponder.panHandlers} style={style}>
      <BaseComponent ref="decorated" {...props} {...rest} />
    </View>
  );
}

after #

render() {
  const {
    onPanBegin,
    onPan,
    onPanEnd,
    panningDecoratorStyle,
    ...props
  } = this.props;

  const style = {
    ...panningDecoratorStyle,
    alignSelf: 'flex-start'
  };

  return (
    <View {...this._panResponder.panHandlers} style={style}>
      <BaseComponent {...props} {...this.state} />
    </View>
  );
}

As you can see, this tiny change reduces our complexity and gets rid of any need for a ref or a hacky setTimeout.

Conclusion #

This is a major win – simpler code, more flexibility!

I have just released a gesture decorator package which contains two decorators ready for use. The package is a work in progress and I have a few ideas to make the gesture decorators way more usable/reusable than they are now. But it is too early to go into details. For now: enjoy!

Leave a Reply

*