wuno
wuno

Reputation: 9865

Finding height of child component based on its dynamic data

Background

I have a parent component which is a collapsing card. When it is past a child component and shows it when it expands, it renders in a scroll box. I tried to adjust the height of the component when it expands by using,

Dimensions.get('window').height

But the problem with that is that it makes the card the page height. I need it to be the height of the child's data plus some padding.

Example

class CardCollapsible extends Component {
    constructor(props) {
        super(props);

        this.state = {
            title: props.title,
            expanded: this.props.expanded,
            animation: new Animated.Value(),
            icon: this.props.icon,
            iconStyles: this.props.iconStyles,
        };

        this.anime = {
            height: new Animated.Value(),
            expanded: false,
            contentHeight: 0,
        };

        this._initContentHeight = this._initContentHeight.bind(this);
        this.getIconStyles = this.getIconStyles.bind(this);
        this.toggle = this.toggle.bind(this);
        this.anime.expanded = props.expanded;
        this.minValue = props.minValue;
        this.openIconStyles = props.openIconStyles;
        this.closedIconStyles = props.closedIconStyles;
    }

    componentWillMount() {
        this.getIconStyles();
    }

    getIconStyles() {
        let iconStyles = null;
        let icon = null;
        if (this.state.expanded) {
            iconStyles = _.extend({}, this.props.closedIconStyles);
            icon = this.props.iconClose;
        } else {
            iconStyles = _.extend({}, this.props.openIconStyles);
            icon = this.props.iconOpen;
        }

        const expanded = !this.state.expanded;
        this.setState({ iconStyles, icon, expanded });
    }

    _initContentHeight(evt) {
        if (this.anime.contentHeight <= 0) {
            this.anime.contentHeight = evt.nativeEvent.layout.height;
            this.anime.height.setValue(this.anime.expanded ? this._getMaxValue() : this._getMinValue());
        }
    }

    _getMaxValue() { return this.anime.contentHeight + 30; }
    _getMinValue() { return this.props.minValue; }

    toggle() {
        this.getIconStyles();

        Animated.timing(this.anime.height, {
            toValue: this.anime.expanded ? this._getMinValue() : this._getMaxValue(),
            duration: 300,
        }).start();
        this.anime.expanded = !this.anime.expanded;
    }

    render() {
        return (
            <Card style={styles.container}>
                <View style={styles.titleContainer}>
                    <CardTitle>{this.state.title}</CardTitle>
                    <TouchableHighlight
                        style={styles.button}
                        onPress={this.toggle}
                        underlayColor="#f1f1f1"
                    >
                        <Icon
                            name={this.state.icon}
                            style={[this.state.iconStyles, styles.icon]}
                        />
                    </TouchableHighlight>
                </View>

                <Animated.View style={{ height: this.anime.height }} onLayout={this._initContentHeight}>
                    <Separator />
                    <CardContent style={styles.CardContent}>
                        {this.props.children}
                    </CardContent>
                </Animated.View>
            </Card>
        );
    }
}

Using the CardCollapsible component.

Data renders in a scroll when card is expanded.

<CardCollapsible>
   <FlatList>...</FlatList>
</CardCollapsible>

Then when changing these lines to

Dimensions.get('window').height

It is taking the height of the screen and will not close.

_initContentHeight(evt) {
        if (this.anime.contentHeight <= 0) {
            this.anime.contentHeight = evt.nativeEvent.layout.height;
            this.anime.height.setValue(this.anime.expanded ? this._getMaxValue() : this._getMinValue());
        }
    }

    _getMaxValue() { return Dimensions.get('window').height; }
    _getMinValue() { return this.props.minValue; }

Question

How can I change the height of the card to be dynamic based on its childs data when rendered?

Upvotes: 4

Views: 1991

Answers (1)

Facundo La Rocca
Facundo La Rocca

Reputation: 3866

What you want to do is a common approach but tricky, since react does not allow communication from children to parent.

How did I accomplish it? By using ref.measures function. Let's take a look.

Child implementation:

export default class MyChild extends Component {
  render() {
    return (
      <TouchableWithoutFeedback onPress={this._onPress} ref={ref => this._options = ref}>

      </TouchableWithoutFeedback>
    );
  }

  _onPress() {
    this._options.measure((fx, fy, width, height, px, py) => {
      if (this.props.setMeasuresOnParent)
        this.props.setMeasuresOnParent({
          relativeX: fx,
          relativeY: fy,
          absoluteX: px,
          absoluteY: py,
          height,
          width
        });
    });
  }
}

Parent implementation:

<MyChild
  setMeasuresOnParent={measures => this.props.showChildren(children_id?, measures.absoluteX, measures.absoluteY)}
/>

In my case, I used a Button-styled child, but you could also use componentDidMount in child to tell the parent its measures.

Upvotes: 3

Related Questions