Tom el Safadi
Tom el Safadi

Reputation: 6796

Formik setFieldValue Inside a Function

If I have a functional component and want to set a value outside of the Formik tag, how would I do that? Below is the code that I have for further clarification.

function XYZScreen() {

    const someFunctionWithLogic = () => {
        // Set the value of the number field here...
    }
    
    return (
        <Screen>
            <Formik>
                <FormField name="number" placeholder="Number" />
             </Formik>
        </Screen>            
    );
}

I have reduced the code to as little as possible to simplify the question. The question might not make sense in the context of the question but I think it is clear what I am asking for.

If you need more code, please let me know, I am happy to provide it if needed.

Upvotes: 16

Views: 96206

Answers (5)

Lakpriya Senevirathna
Lakpriya Senevirathna

Reputation: 5119

You can also do this easily with initialValues

 <Formik
    initialValues={{
      "fieldName": this.state.value,
    }}
    enableReinitialize
    ...
 >
 ...
</Formik>

Also for functional components, you can do this with useEffect inside Formik, set the value using const [value,setValue] = useState()

<Formik
  initialValues={{}}
  onSubmit={(values, { resetForm }) => _submit(values, resetForm)}
  validationSchema={buildValidationSchema(actionItem.fields)}
>
{({ setFieldValue, handleSubmit, values, errors, touched }) => {
    useEffect(() => {
       setFieldValue("fieldName",value); 
    }, [value]); 
...
</Formik>

Upvotes: 1

I just faced similar issue myself, and came up with another solution extending comment from Seth Lutske. You could combine using useFormik together with manual creation of Formik context. useFormik is very powerful, but it's downside is that it does not create a context, thus you cannot use useField or useFormikContext in child components, and need to manually pass them all the related props (value, onChange, onBlur etc).

What you can do is to create a Formik context object using useFormik, and pass context down manually using <FormikProvider>. With this approach, you can continue using all the fields as if they were wrapped in <Formik>

import { useFormik, FormikProvider } from 'formik'

function XYZScreen() {
    const formikProps = useFormik({
        initialValues,
        validationSchema,
        onSubmit: yourSubmitFunction,
        ...etc
    })

    const someFunctionWithLogic = () => {
        // Set the value of the number field here:
        formikProps.setFieldValue("number", someNumber)
    }
    
    return (
        <Screen>
          {/* FormikProvider component instead of Formik */}
          <FormikProvider value={formikProps}>  
            <FormField 
              name="number" 
              placeholder="Number" 
            />
          </FormikProvider>  
        </Screen>            
    );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Upvotes: 3

Mohamed Atef
Mohamed Atef

Reputation: 133

The best way to achieve this is by passing setFieldValue function to the function which you want so the code should look like this

function XYZScreen() {

const someFunctionWithLogic = (value, setFieldValue) => {
    // Set the value of the number field here...
    setFieldValue('price', value)
}

return (
    <Screen>
        <Formik>
            <FormField name="number" placeholder="Number" onChange={(e) => { handleChange(e); someFunctionWithLogic(value, setFieldValue ); }} />
         </Formik>
    </Screen>            
);
}

Reference https://web-brackets.com/discussion/12/how-to-use-setfieldvalue-from-outside-render-function-formik

Upvotes: 2

Richard Zhan
Richard Zhan

Reputation: 500

Why don't you try resetting the form using initialProps?

function XYZScreen() {

    const [number, setNumber] = useState()

    const someFunctionWithLogic = () => {
        // Set the value of the number field here...
        setNumber(...)
    }
    
    return (
        <Screen>
            <Formik initialProps={{number}}>
                <FormField name="number" placeholder="Number" />
             </Formik>
        </Screen>            
    );
}

Another suggestion is to pass that number to the inner component of Formik:


function SomeChild(props) {
    const {number, setFieldValue} = props;
    
    useEffect(() => {
       setFieldValue('number', number);
    }, [number]);

    return (
       <FormField name="number" placeholder="Number" />
    )
}

function XYZScreen() {

    const [number, setNumber] = useState()

    const someFunctionWithLogic = () => {
        // Set the value of the number field here...
        setNumber(...)
    }
    
    return (
        <Screen>
            <Formik>
                {(props) => <SomeChild number={number} {...props} />
             </Formik>
        </Screen>            
    );
}

Upvotes: -1

Seth Lutske
Seth Lutske

Reputation: 10752

You can use the useFormik hook instead:

import { useFormik } from 'Formik'

function XYZScreen() {

    const formikProps = useFormik({
        initialValues,
        validationSchema,
        onSubmit: yourSubmitFunction,
        ...etc
    })

    const someFunctionWithLogic = () => {
        // Set the value of the number field here:
        formikProps.setFieldValue("number", someNumber)
    }
    
    return (
        <Screen>
            {/* No need for Formik component */}
            <FormField 
                name="number" 
                placeholder="Number" 
                value={formikProps.values.number} // or whatever the value is
                onChange={formikProps.handleChange}
            />
        </Screen>            
    );
}

So you're basically setting up all your form props in the function body, and you have access to them there. Formik has a lot of great helper hooks and functions, I highly recommend combing through the docs.

Edit: Another way

If you really like the Formik tag, you can keep using it. Just create a Formik wrapper component, and use your custom logic in a descendant using useFormikContext:

// FormWrapper.js

const FormWrapper = () => (
  <Formik
    initialValues={initialValues}
    validationSchema={validationSchema}
    onSubmit={yourOnSubmitFunction}
  >
    <SomeChild />
  </Formik>
)

// SomeChild.js

const SomeChild = () => {

  // returns all values and methods from your Formik tag
  const formikProps = useFormikContext()

  const someFunctionWithLogic = () => {
    formikProps.setFieldValue("number", someNumber)
  }
    
  return (
    <Screen>
      <FormField 
        name="number" 
        placeholder="Number" 
        value={formikProps.values.number} // or whatever the value is
        onChange={formikProps.handleChange}
      />
    </Screen>            
    );
}

Upvotes: 28

Related Questions