FaridSafi / react-native-gifted-form

📝 « One React-Native form component to rule them all »
MIT License
1.44k stars 214 forks source link

Dynamic form content #11

Open transitive-bullshit opened 8 years ago

transitive-bullshit commented 8 years ago

First off this library is really great.

I ran into an issue where Modals with custom content dependent on container state aren't re-rendered due to the ModalWidget cloning the elements once at mount time. I was hoping to have a more dynamic modal which would show / hide a TextAreaWidget dependent on the value of a SwitchWidget ala below:

         <GiftedForm.ModalWidget
            ref="customMessageModal"
            title='Custom Message'
            displayValue='hasCustomMessage'
            underlayColor='royalblue'
            transformValue={(value) => (value ? 'on' : 'off') }
          >
            <GiftedForm.SeparatorWidget />

            <GiftedForm.SwitchWidget
              name='hasCustomMessage'
              title='Custom Message? (+ $1.00)'
              value={this.state.hasCustomMessage}
              onValueChange={(value) => {
                this.setState({ hasCustomMessage: value })
              }}
            />

            {this.state.hasCustomMessage ? <GiftedForm.TextAreaWidget
              name='customMessage'
              value={this.state.customMessage}
              placeholder='Custom Message'
              maxLength={120}
              multiline={true}
              style={styles.customMessage}
              onChangeText={(value) => {
                this.setState({ customMessage: value })
              }}
            /> : <View />}

            <GiftedForm.NoticeWidget title='Optionally add a custom welcome message that will be sent before any other messages. (Max 120 characters)' />
          </GiftedForm.ModalWidget>

Any idea how to make the modal contents more dynamic?

Thanks!

FaridSafi commented 8 years ago

Hello @fisch0920, I can't test your code right now but I think your onValueChange is overwritten by https://github.com/FaridSafi/react-native-gifted-form/blob/master/widgets/SwitchWidget.js#L61 You must replace line 61 by something like :

onValueChange={(value) => {
  this._onChange(value);
  this.props.onChange(value);
}} 

then to do

onChange={(value) => {
  this.setState({ hasCustomMessage: value })
}}

then to do something similar for TextAreaWidget

If it works, please submit a pull request

transitive-bullshit commented 8 years ago

Hey @FaridSafi, thanks for the quick feedback and I know what you mean. I've already changed my fork to allow onValueChange overrides in addition to calling WidgetMixin._onChange, but the base issue remains that updating the parent state doesn't update the cloned modal's child states.

transitive-bullshit commented 8 years ago

If I change ContainerMixin as follows, dynamic content within the form itself and modals works as expected.

From:

componentWillMount() {
    this._childrenWithProps = React.Children.map(this.props.children, (child) => {
      return React.cloneElement(child, {
        formStyles: this.props.formStyles,
        openModal: this.props.openModal,
        formName: this.props.formName,
        navigator: this.props.navigator,
        onFocus: this.handleFocus,
        onBlur: this.handleBlur,
      });
    });
  },

  _renderContainerView() {
    if (this.props.scrollEnabled === true) {
      return (
        <ScrollView
          ref='container'
          style={[(this.props.isModal === false ? styles.containerView : styles.modalView), this.props.style]}
          automaticallyAdjustContentInsets={false}
          keyboardDismissMode='on-drag'
          keyboardShouldPersistTaps={true}

          onTouchStart={this.props.scrollOnTap === true ? this._onTouchStart : null}
          onScroll={this.props.scrollOnTap === true ? this._onScroll : null}
          scrollEventThrottle={this.props.scrollOnTap === true ? 200 : 0}

          {...this.props}
        >
          {this._childrenWithProps}
        </ScrollView>
      );
    }
    return (
      <View
        ref='container'
        style={[(this.props.isModal === false ? styles.containerView : styles.modalView), this.props.style]}
        keyboardDismissMode='on-drag' // its working on View ?

        {...this.props}
      >
        {this._childrenWithProps}
      </View>
    );
  },

To:

componentWillMount() {
    this._childrenWithProps = () => React.Children.map(this.props.children, (child) => {
      return React.cloneElement(child, {
        formStyles: this.props.formStyles,
        openModal: this.props.openModal,
        formName: this.props.formName,
        navigator: this.props.navigator,
        onFocus: this.handleFocus,
        onBlur: this.handleBlur,
      });
    });
  },

  _renderContainerView() {
    if (this.props.scrollEnabled === true) {
      return (
        <ScrollView
          ref='container'
          style={[(this.props.isModal === false ? styles.containerView : styles.modalView), this.props.style]}
          automaticallyAdjustContentInsets={false}
          keyboardDismissMode='on-drag'
          keyboardShouldPersistTaps={true}

          onTouchStart={this.props.scrollOnTap === true ? this._onTouchStart : null}
          onScroll={this.props.scrollOnTap === true ? this._onScroll : null}
          scrollEventThrottle={this.props.scrollOnTap === true ? 200 : 0}

          {...this.props}
        >
          {this._childrenWithProps()}
        </ScrollView>
      );
    }
    return (
      <View
        ref='container'
        style={[(this.props.isModal === false ? styles.containerView : styles.modalView), this.props.style]}
        keyboardDismissMode='on-drag' // its working on View ?

        {...this.props}
      >
        {this._childrenWithProps()}
      </View>
    );
  }

Note that this is just making the childrenWithProps cloning dynamic instead of only at mount time. This change is necessary for my use case to function properly as I want to rely on external state to update the form's contents, not just the form's internal state representation. @FaridSafi what do you think of this change? Will it have any unexpected side effects?

FaridSafi commented 8 years ago

Looks good, can you enable it as an option (something like dynamicForm={true}) and submit a PR please ? We can include this in next version.

FaridSafi commented 8 years ago

Also, I think you should use widgetStyles prop instead of style for the TextAreaWidget

widgetStyles={{
  textArea: styles.customMessage
}}