2

I'm new to Formik and React Native. Currently, my goal is to submit a form via the Navigation header. All the examples I found are to submit the form from the screen page itself. I need help figure out how to properly use the handleSubmit and onSubmit property and setParams to pass the value to the navigation header properly.

Right now, I'm stuck by not sending the values from the form to the useCallBack hook.

import React, {useState, useEffect, useCallback} from 'react';
import {StyleSheet, View, Button, Text} from 'react-native';
import {Input} from 'react-native-elements';
import {useDispatch} from 'react-redux';
import Colors from '../../constants/Colors';
import {
  HeaderButtons,
  HeaderButton,
  Item,
} from 'react-navigation-header-buttons';
import {Ionicons} from '@expo/vector-icons';
import {CommonActions} from '@react-navigation/native';
import * as prodActions from '../../store/actions/products';
import {Formik} from 'formik';
import * as Yup from 'yup';

const AddProduct = ({navigation}) => {

  const dispatch = useDispatch();

  const submitHandler = useCallback(() => {
    dispatch(prodActions.addProduct(value));
  }, [value]);

  useEffect(() => {
    navigation.dispatch(CommonActions.setParams({submitForm: submitHandler}));
  }, [submitHandler]);

  return (
    <View style={styles.screen}>
      <Formik
        initialValues={{title: '', description: '', imageUrl: ''}}
        validationSchema={Yup.object({
          title: Yup.string().required('please input your title'),
          description: Yup.string().required('please input your description'),
          imageUrl: Yup.string()
            //.email('Please input a valid email.')
            .required('Please input an email address.'),
        })}
        onSubmit={submitHandler}
      >
        {({
          handleChange,
          handleBlur,
          handleSubmit,
          values,
          touched,
          errors,
        }) => (
          <View>
            <Input
              label="Title"
              labelStyle={{color: Colors.accent}}
              onChangeText={handleChange('title')}
              onBlur={handleBlur('title')}
              value={values.title}
              // errorMessage={errors.title}
            />
            {touched.title && errors.title ? (
              <Text style={styles.error}>{errors.title}</Text>
            ) : null}
            <Input
              label="Description"
              labelStyle={{color: Colors.accent}}
              onChangeText={handleChange('description')}
              onBlur={handleBlur('description')}
              value={values.description}
            />
            {touched.description && errors.description ? (
              <Text style={styles.error}>{errors.description}</Text>
            ) : null}
            <Input
              label="Image URL"
              labelStyle={{color: Colors.accent}}
              onChangeText={handleChange('imageUrl')}
              onBlur={handleBlur('imageUrl')}
              value={values.imageUrl}
            />
            {touched.imageUrl && errors.imageUrl ? (
              <Text style={styles.error}>{errors.imageUrl}</Text>
            ) : null}
            <Button onPress={handleSubmit} title="Submit" />
          </View>
        )}
      </Formik>
    </View>
  );
};

const IoniconsHeaderButton = (props) => (
  <HeaderButton
    IconComponent={Ionicons}
    iconSize={23}
    color="white"
    {...props}
  />
);

export const AddProductHeaderOptions = (navData) => {
  const updateForm = navData.route.params.submitForm;
  return {
    headerRight: () => {
      return (
        <HeaderButtons HeaderButtonComponent={IoniconsHeaderButton}>
          <Item title="add" iconName="save-outline" onPress={updateForm} />
        </HeaderButtons>
      );
    },
  };
};

export default AddProduct;

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    marginHorizontal: 20,
    marginTop: 20,
  },
  error: {
    marginLeft: 8,
    fontSize: 14,
    color: 'red',
  },
});
Yuze Sun
  • 63
  • 1
  • 6
  • `const submitHandler = useCallback( ({values}) => { ... }, [] );` ... no dependency as values not in this scope – xadm Apr 21 '21 at 19:53

3 Answers3

0

You're probably using "value" variable with an empty value. You must ref all your form data, inside your < Formik > , let's use street, for an example:

value={values.street}
error={touched.street && !isSubmitting && errors.street}
returnKeyType="next"
onSubmitEditing={() => refs.number.current?.focus()}
ref={refs.street}

So declare this ref variable first:

const refs = {
street: useRef<any>(null),
number: useRef<any>(null),
zipCode: useRef<any>(null),
//....

If you don't wanna go with REF, so at least declare the variable and try it, like that:

const [value, setValue] = useState();

Also, the VALUE name is not a good variable name because there are a lot of native things using it. But, considering that you must have taken this from an example, use useState with your form or object and you should be good.

const [formx, setFormx] = useState();
mmelotti
  • 336
  • 3
  • 11
0

For those who use React-Navigation 5 and Formik 1.5.x+ Here is a good solution. Declare a ref variable outside/inside of your function component to later Attach to your

let formRef;//or you can use const formRef = useRef(); inside function component

If you declare formRef outside your function component use React hook {useRef} to update the ref

formRef = useRef();

Render

<Formik innerRef={formRef} />

And finally in your header button call this

navigation.setOptions({
headerRight: () => (
    <Button onPress={() => {
        if (formRef.current) {
           formRef.current.handleSubmit()
        }
    }}/>
  )
});
Robin Khan
  • 127
  • 1
  • 10
0

Another solution, which I think could be better is to use the useFormik, which gives you access to all the form handlers and form meta:

  const {
    handleSubmit,
    handleChange,
    isValid,
  } = useFormik({
    initialValues: {...},
    validationSchema: yupValidationSchema,
    onSubmit: (formValues) => {
      //save here...
    }
  });

Then you can simply pass the handleSubmit reference to your right header navigation using the useLayoutEffect as proposed in the official documentation:

useLayoutEffect(() => {
  navigation.setOptions({
    headerRight: () => {
      return (
        <HeaderButtons HeaderButtonComponent={IoniconsHeaderButton}>
          <Item title="add" iconName="save-outline" onPress={handleSubmit}/>
       </HeaderButtons>
    );
  }
});

});

This approach is clear in my opinion, since you don't need to handle all the structural disadvantages of using the renderProps approach with

<Formkik>
  {({ handleSubmit }) => (
   jsx here...
  )}
</Formik>
César Riva
  • 161
  • 1
  • 2