satya164 / react-native-tab-view

A cross-platform Tab View component for React Native
MIT License
5.13k stars 1.07k forks source link

TextInput focus and swipe TabView impact each other #473

Closed andyngdz closed 5 years ago

andyngdz commented 6 years ago

Current behaviour

When I'm swiping tab with TextInput inside. Inside of moving to next tab it focuses to TextInput

Expected behaviour

It should move to next tab without focusing to TextInput

Code sample

UserPage.js

import { AppVariables } from "../Lib/app.variables";
import {
  View,
  ScrollView,
  Image,
  ImageBackground,
  Tile,
  Title,
  Text,
  Icon,
  Caption,
  Spinner,
  Subtitle,
  TouchableOpacity
} from "@shoutem/ui";
import ComingSoon from "../Components/ComingSoon";
import Images from "../Services/Images";
import React, { Component } from "react";
import styles from "./Styles/UserPageStyle";
import TabBarExtended from "../Components/TabBarExtended";
import UdUserAddressesScreen from "../Containers/UdUserAddressesScreen";
import UdUserInformationScreen from "../Containers/UdUserInformationScreen";
import UdUserNotificationsScreen from "../Containers/UdUserNotificationsScreen";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
export default class UserPage extends Component {
  constructor(props) {
    super(props);

    this.state = {
      tabControl: {
        listPages: [
          {
            title: "Information",
            component: UdUserInformationScreen
          },
          {
            title: "Notification",
            component: UdUserNotificationsScreen
          },
          {
            title: "Address",
            component: UdUserAddressesScreen
          }
        ]
      }
    };
  }

  componentDidMount() {}

  render() {
    if (this.props.user.userInfo) {
      return (
        <KeyboardAwareScrollView style={styles.container}>
          <View style={styles.userHeaderContent}>
            <TouchableOpacity
              style={styles.userTopBackGround}
              onPress={this.props.changeUserCover}
            >
              <ImageBackground
                style={styles.userCoverBackGround}
                source={{ uri: this.props.user.userCover.photoID.photoURL }}
              >
                <View style={styles.editView}>
                  <View
                    style={styles.editViewContentForCover}
                    styleName="horizontal h-center v-center rounded-corners"
                  >
                    <Icon name="take-a-photo" style={styles.editIcon} />
                  </View>
                </View>
              </ImageBackground>
            </TouchableOpacity>
            <TouchableOpacity onPress={this.props.changeUserPhoto}>
              <View>
                <Image
                  style={styles.userAvatar}
                  styleName="avatarCircle"
                  source={{ uri: this.props.user.userAvatar.photoID.photoURL }}
                />
                <View style={styles.editView}>
                  <View
                    style={styles.editViewContentForPhoto}
                    styleName="horizontal h-center v-center rounded-corners"
                  >
                    <Icon name="take-a-photo" style={styles.editIcon} />
                  </View>
                </View>
              </View>
            </TouchableOpacity>
          </View>

          <View
            styleName="md-gutter v-center space-between horizontal"
            style={AppVariables.backgroundColor.green}
          >
            <Tile styleName="clear textContentWhite">
              <Text>{this.props.user.userInfo.name}</Text>
              <Caption>{this.props.user.userBase.email}</Caption>
            </Tile>
            <Icon name="checkbox-on" style={styles.verifiedIcon} />
          </View>
          <TabBarExtended data={this.state.tabControl} />
        </KeyboardAwareScrollView>
      );
    } else {
      return (
        <View style={styles.container}>
          <Spinner />;
        </View>
      );
    }
  }
}

TabBarExtended.js

import { AppVariables, AppVariablesNative } from "../Lib/app.variables";
import { TabViewAnimated, TabBar, SceneMap } from "react-native-tab-view";
import React, { Component } from "react";
import styles from "./Styles/TabBarExtendedStyle";

/**
 * The default styling for Tabbar
 */
const initialLayout = {
  height: 300,
  width: AppVariables.base.width
};

export default class TabBarExtended extends Component {
  constructor(props) {
    super(props);

    /**
     * The defaullt config for tabBar
     */
    this.state = {
      index: 0,
      routes: this.props.data.listPages.map((page, index) => {
        return {
          key: index.toString(),
          title: page.title
        };
      })
    };

    /**
     * Handle the state changing
     * Set new index
     */
    this.handleIndexChange = index => {
      this.setState({ index });
    };

    /**
     * Render the header :)
     */
    this.renderHeader = props => {
      return (
        <TabBar
          scrollEnabled={true}
          style={AppVariables.backgroundColor.white}
          indicatorStyle={AppVariables.backgroundColor.green}
          labelStyle={AppVariables.font.fontColor.black}
          {...props}
        />
      );
    };

    /**
     * Setup Scene
     */
    let sceneMapConfig = {};
    for (var i = 0; i < this.props.data.listPages.length; i++) {
      let page = this.props.data.listPages[i];
      sceneMapConfig[i.toString()] = page.component;
    }

    /**
     * Render scene
     */
    this.renderScene = ({ route }) => {
      return React.createElement(sceneMapConfig[route.key]);
    };
  }

  render() {
    return (
      <TabViewAnimated
        style={styles.container}
        navigationState={this.state}
        renderScene={this.renderScene}
        renderHeader={this.renderHeader}
        onIndexChange={this.handleIndexChange}
        initialLayout={initialLayout}
      />
    );
  }
}

UdUserInformationScreen.js

import { connect } from "react-redux";
import { UDUserInformationModel } from "@ang/model";
import { View } from "@shoutem/ui";
import React, { Component } from "react";
import UdUserInformation from "../Components/UdUserInformation";
import User from "../Services/User";
import UserActions from "../noodles/actions/userActions";

// Styles
import styles from "./Styles/UdUserInformationScreenStyle";

class UdUserInformationScreen extends Component {
  constructor(props) {
    super(props);
    this.updateMyInformation = this.updateMyInformation.bind(this);
  }

  /**
   * Update user's information
   */
  updateMyInformation(success, error) {
    this.udUserInformationModel = new UDUserInformationModel();
    this.udUserInformationModel.id = this.props.user.userInfo.id;
    this.udUserInformationModel.name = [
      this.props.form.upUserInformation.values.firstName,
      this.props.form.upUserInformation.values.lastName
    ].join(" ");
    this.udUserInformationModel.firstName = this.props.form.upUserInformation.values.firstName;
    this.udUserInformationModel.lastName = this.props.form.upUserInformation.values.lastName;
    this.udUserInformationModel.phoneCode = this.props.form.upUserInformation.values.userPhone.phoneCode;
    this.udUserInformationModel.phoneNumber = this.props.form.upUserInformation.values.userPhone.phoneNumber;
    User.updateUserInformation(this.udUserInformationModel)
      .then(response => {
        success(response.data);
        this.props.updateMyInformation(response.data);
      })
      .catch(err => {
        error(err);
      });
  }

  render() {
    return (
      <UdUserInformation
        updateMyInformation={this.updateMyInformation}
        form={this.props.form}
        user={this.props.user}
      />
    );
  }
}

const mapStateToProps = state => {
  return {
    user: state.user,
    form: state.form
  };
};

const mapDispatchToProps = dispatch => {
  return {
    /**
     * We need to update new user info
     * Dispatch it!
     * @param newUserInfo The new user info got from server
     */
    updateMyInformation: userInfo => {
      dispatch(UserActions.userUpdatedInformation(userInfo));
    }
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(
  UdUserInformationScreen
);

UdUserInformation.js

import { AppVariables } from "../Lib/app.variables";
import {
  renderTextInputExtended,
  renderPhoneInputExtended
} from "../Services/HOC";
import { Alert } from "react-native";
import { MessageBarService } from "../Services/MessageBarService";
import { View, TextInput } from "@shoutem/ui";
import ButtonLoading from "../Components/ButtonLoading";
import Form from "../Services/Form";
import FormValidation from "../Components/FormValidation";
import PhoneInputExtended from "../Components/PhoneInputExtended";
import React, { PureComponent } from "react";
import styles from "./Styles/UdUserInformationStyle";
import TextInputExtended from "../Components/TextInputExtended";

export default class UdUserInformation extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      buttonLoading: {
        isLoading: false,
        title: "UPDATE MY INFORMATION",
        click: clickDone => {
          this.props.updateMyInformation(
            userInfoUpdated => {
              clickDone();
              Alert.alert("Success", "Your information was updated");
            },
            err => {
              clickDone();
              console.error(err);
            }
          );
        }
      }
    };

    /**
     * Init form for updating UserInformation
     */
    this.upUserInformationForm = [
      renderTextInputExtended(TextInputExtended, {
        styleName: "border md-gutter",
        name: "firstName",
        validate: ["required"],
        placeholder: "Your first name"
      }),
      renderTextInputExtended(TextInputExtended, {
        styleName: "border md-gutter-left md-gutter-right",
        name: "lastName",
        validate: ["required"],
        placeholder: "Your last name"
      }),
      renderPhoneInputExtended(PhoneInputExtended, {
        name: "userPhone",
        validate: ["phoneRequired"],
        for: {
          phoneCode: {
            styleName:
              "border removeBorderRadiusTopRight removeBorderRadiusBottomRight removeBorderRight",
            placeholder: "Phone code",
            returnKeyType: "done",
            keyboardType: "phone-pad"
          },
          phoneNumber: {
            styleName:
              "border fullWidth removeBorderRadiusTopLeft removeBorderRadiusBottomLeft",
            keyboardType: "phone-pad",
            returnKeyType: "done",
            placeholder: "Phone number"
          }
        }
      })
    ];

    /**
     * Init default information
     */
    this.upUserInformationInitialValues = {
      firstName: this.props.user.userInfo.firstName,
      lastName: this.props.user.userInfo.lastName,
      userPhone: {
        phoneCode: this.props.user.userInfo.phoneCode,
        phoneNumber: this.props.user.userInfo.phoneNumber
      }
    };
  }

  componentDidMount() {}

  render() {
    return (
      <View style={AppVariables.backgroundColor.white}>
        <FormValidation
          initialValues={this.upUserInformationInitialValues}
          style={AppVariables.backgroundColor.white}
          formID="upUserInformation"
          formControls={this.upUserInformationForm}
        />
        <ButtonLoading
          disabled={!Form.checkFormsValid(this.props.form.upUserInformation)}
          styleName="noBorderRadius md-gutter"
          data={this.state.buttonLoading}
        />
      </View>
    );
  }
}

TextInputExtended.js

import styles from "./Styles/TextInputExtendedStyle";
import React, { Component } from "react";
import { TextInput } from "@shoutem/ui";

export default class TextInputExtended extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <TextInput
        {...this.props}
        value={this.props.value}
        onBlur={e => {
          this.props.input.onBlur();
          if (this.props.event && this.props.event.onBlur) {
            this.props.event.onBlur(e.nativeEvent.text, this.props);
          }
        }}
        onChangeText={value => this.props.input.onChange(value)}
        clearButtonMode="while-editing"
      />
    );
  }
}

Screenshots (if applicable)

https://plancharity.com/wp-content/uploads/2018/03/ezgif.com-video-to-gif.gif Sorry the gif size is quite big so github won't accept to display it. Kindly please click to the link and see it.

What have you tried

I don't know how to fix it.

Your Environment

I'm using:

software version
ios 11.2.2
react-native 0.53.0
react-native-tab-view 0.0.74
node v8.9.4
yarn 1.5.1
logicallydimp23 commented 6 years ago

Is this fixed? I have the same issue

satya164 commented 5 years ago

Hey, I just released a new alpha 2.0.0-alpha.0 of the library. It's rewritten using react-native-gesture-handler and react-native-reanimated addresses a many platform specific bugs and performance problems. The documentation is updated as well.

Please try the new version and see if it addresses your issue. If not, please open a new issue following the issue template.