0

I am newbie to react native with expo, I have created a functional component that tries to do things similar to the behavior of the TextInput of react-native-paper, with animations and so on.

The problem is I have several instances of this component on the same screen and when I press on the first one to write to it, the rest of them also get focused. And if I fill any text in the first one, the second one triggers the notifications without having been touched.

I am using formik, Yup and typescript.

This is the initial state

When I press on the first component, the rest get focus

When I write on the first component, the validation triggers on the second one.

This is how I call the component in the form:

    ...
    <ScrollView>
        <FloatingLabelInput
          label="Ciudad"
          marginBottom={20}
          onChangeText={text => {
            formik.setFieldValue('city', text)
          }}
          value={formik.values.city}
          error={formik.errors.city}
        />
        <FloatingLabelInput
          label="Calle principal"
          marginBottom={20}
          onChangeText={text => {
            formik.setFieldValue('mainStreet', text)
          }}
          value={formik.values.mainStreet}
          error={formik.errors.mainStreet}
        />
    </ScrollView>
    ....

The component definition:

    ...
export default function FloatingLabelInput(props: FloatingLabelInputProps) {
  const { label, style, onChangeText, value, error, containerStyle, border } = props
  
  useEffect(() => {
    if (value === '') {
      resetViewTransition.start()
      resetLabelTransition.start()
    }
  }, [value])

  const handleFocus = () => {
    animateViewTransition.start()
    animateLabelTransition.start()
  }

  const handleBlur = () => {
    if (value === '') {
      resetViewTransition.start()
      resetLabelTransition.start()
    }
  }

  const handleTextChange = (text: string) => {
    if (text.length > 0) {
      animateViewTransition.start()
      animateLabelTransition.start()
    }
    onChangeText && onChangeText(text)
  }

  return (
    <View style={styles(marginBottom).mainContainer}>
      <Animated.View style={[styles().container, error ? styles().containerError : border !== undefined && { borderColor: viewAnimationBorderColor(border) }, containerStyle]}>
        <Animated.Text style={[styles().labelStyle, { top: labelAnimationTop, fontSize: labelAnimationFontSize, color: labelAnimationColor }]}>{label}</Animated.Text>
        {secureTextEntry && <Icon name={passwordShown ? 'eye-outline' : 'eye-off-outline'} size={25} style={styles().eye} onPress={() => setPasswordShown(!passwordShown)} />}
        <TextInput
          selectionColor={error ? colors.fireEngineRed : border ? border : colors.nightRider}
          underlineColorAndroid="transparent"
          style={[styles().input, { color: error ? colors.fireEngineRed : border ? border : colors.nightRider }, style]}
          onChangeText={handleTextChange}
          value={value}
          onFocus={handleFocus}
          onBlur={handleBlur}
          keyboardType={defineKeyBoardType}
          maxLength={maxLength}
          secureTextEntry={secureTextEntry && !passwordShown}
          keyboardAppearance="dark"
        />
      </Animated.View>
      {error && (
        <HelperText type="error" style={styles().errorText}>
          {error}
        </HelperText>
      )}
    </View>
  )
}
Danny P
  • 93
  • 2
  • 13

2 Answers2

1

Your "everything starts validating as soon as I change one field" is a problem I've faced before. What seems to work for me is to retain a state variable and ONLY set that state variable after the first time handleSubmit is called.

Formik validation is dependent on the state variable, which is initially false (so it doesn't)

    <Formik
      enableReinitialize={true}
      validateOnMount={false}
      validateOnChange={this.state.validationEnabledOnChange}
      initialValues=...
      onSubmit={this.submitHandler}
    >

Submit handle changes to start validating now

  submitHandler = (values, actions) => {
    // Start validating all fields on change now..
    this.setState({
      validationEnabledOnChange: true
    })

    ... try to handle submit
   }

I'm not sure about your focus problem. I can't see code above would cause focus to occur. I did, for a time, use react-native-formik's withNextInputAutoFocusForm method which automagically did a lot of this for me. I suppose you could study their source code to see if there's something you can glean from that. The project is starting to age and hasn't been keeping up, so some of their other Higher-order-components were causing me issues and I had to get rid of a lot of my dependencies to the project, so I wouldn't suggest you try to start using it whole-sale..

Source is here: https://github.com/bamlab/react-native-formik

Atmas
  • 2,389
  • 5
  • 13
  • Thanks @Atmas your answer guided me to the point – Danny P May 16 '21 at 00:15
  • Glad I could help you. As a beginner I found much of this very challenging. Still stumps me sometimes. Did you figure out a solution to your focus problem? I wouldn't mind hearing what the fix was there. Also please do upvote and/or accept my answer to pass on some kudos points when you can! – Atmas May 16 '21 at 02:45
1

If someone who is with the same bug/error, I give them my solution:

First, to fix the animations, which are doing at the same time, I pulled the line:

const animationValue = new Animated.Value(0)

From the 2 animation files I had created (for the view and de label) and put them inside the generic component. In this way I send animationValue as a parameter to its corresponding animation files.

  const animationValue = new Animated.Value(0)

  useEffect (() => {
    if (value === '') {
      resetViewTransition(animationValue).start()
      resetLabelTransition(animationValue).start()
      return
    }
    animateViewTransition(animationValue).start()
    animateLabelTransition(animationValue).start()
  }, [value])

  const handleFocus = () => {
    animateViewTransition(animationValue).start()
    animateLabelTransition(animationValue).start()
  }

  const handleBlur = () => {
    if (value === '') {
      resetViewTransition(animationValue).start()
      resetLabelTransition (animationValue).start()
    }
  }

  const handleTextChange = (text: string) => {
    if (text.length> 0) {
      animateViewTransition(animationValue).start ()
      animateLabelTransition(animationValue).start ()
    }
    onChangeText && onChangeText(text)
  }

I have based on this question and its answer (whit the example) to fix the animations:

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

https://snack.expo.io/@karthik.01/react.children

Then for the second problem, I have to use the other answer of my question https://stackoverflow.com/a/67543354/4855608

And with those two adjustments I got the independence of each component in the form.

Danny P
  • 93
  • 2
  • 13
  • Thanks for sharing the final product! This will be good to review. I've been meaning to try some of this myself. – Atmas May 18 '21 at 01:40