Search code examples
javascriptreactjsreact-nativereact-animated

How can I make this custom button component reusable across different controls?


I have this custom component class that I apply to my React Native Buttons and it has the expected behavior of scaling in and out (adding animation to shrink and resize) which is the behavior I want. However, I want the same animation for other controls in my app like Cards for example. I was wondering, how can I change this class to make it more extensible?

Here's my code:

import React from "react";
import { StyleSheet, Text, TouchableWithoutFeedback, Animated} from "react-native";
import Colors from "./Colors";
import Fonts from "./Fonts";

export default class TouchableBounce extends React.Component {

  constructor(props) {
    super(props);

    this.handlePressIn = this.handlePressIn.bind(this);
    this.handlePressOut = this.handlePressOut.bind(this);

  }

  componentWillMount() {
    this.animatedValue = new Animated.Value(1);
  }

  handlePressIn() {
    Animated.spring(this.animatedValue, {
      toValue: .5
    }).start()
  }

  handlePressOut() {
    Animated.spring(this.animatedValue, {
      toValue: 1,
      friction: 5,
      tension: 40
    }).start()
  }

  render() {

    const animatedStyle = {
        transform: [{ scale: this.animatedValue}]
      }

    const {
        disabled,
        text,
        color,
        backgroundColor,
        style,
        showArrow,
        testID,
        buttonStyle

    } = this.props;

    return (
      <TouchableWithoutFeedback
        onPressIn={this.handlePressIn}
        onPressOut={this.handlePressOut}
        disabled={disabled}
        style={[styles.buttonContainer, style]}
        testID={testID || `button_${text}`}
      >
        <Animated.View
          style={[
            styles.button,
            disabled ? { opacity: 0.5 } : {},
            { backgroundColor },
            buttonStyle,
            animatedStyle
          ]}
        >
          <Text style={[styles.buttonText, { color }]}>{text.toUpperCase()}</Text>
          {showArrow && (
            <Text
              style={{
                fontSize: 20,
                fontWeight: "bold",
                color: "white",
                fontFamily: "system font",
                marginBottom: 1
              }}
            >
              {" "}
              →
            </Text>
          )}
        </Animated.View>
      </TouchableWithoutFeedback>
    );
  }
} 

TouchableBounce.defaultProps = {
  disabled : false,
  color : Colors.white,
  backgroundColor : Colors.mainAccent,
  style : {},
  showArrow : false,
  testID : "",
  buttonStyle : {}
}

const styles = StyleSheet.create({
  buttonContainer: {
    alignSelf: "stretch",
    marginTop: 35,
    marginBottom: 35
  },
  button: {
    borderRadius: 4,
    padding: 20,
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center"
  },
  buttonText: {
    textAlign: "center",
    fontFamily: Fonts.montserratBold,
    fontSize: 16
  }
});

EDIT: I have a question on where I should make the change for nesting the component.Inside my Home render function there's this snippet

const card = active ? (
      <ActiveCard purchase={active} />

    ) : (
      <InactiveCard />
    );

and inside my return of that render, there is this snippet

{!this.props.foo && (
                <View>
                  <TouchableOpacity
                    testID={"TOUCHABLE_CARD"}
                    onPress={() => {
                      this.tapCard(active);
                    }}
                  >
                    {card}
                  </TouchableOpacity>
                </View>
              )}

Where should I wrap the TouchableBounce? In both places or one of those places?


Solution

  • Try passing them as children of TouchableBounce

    <TouchableBounce>
      <CardView/>
    </TouchableBounce>
    

    In the TouchableBounce render them as

    <TouchableWithoutFeedback
      onPressIn={this.handlePressIn}
      onPressOut={this.handlePressOut}
      disabled={disabled}
      style={[styles.buttonContainer, style]}
      testID={testID || `button_${text}`}
    >
      <Animated.View
        style={[
          styles.button,
          disabled ? { opacity: 0.5 } : {},
          { backgroundColor },
          buttonStyle,
          animatedStyle
        ]}
      >
        {this.props.children}//Here is the cardview that you have sent
      </Animated.View>
    </TouchableWithoutFeedback>
    

    Edit:

    For clear understanding iam attaching a working demo Expo demo

    and also the official docs React.Children