jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
33.98k stars 2.79k forks source link

Using initialValues with client-side session #2595

Closed blms closed 4 years ago

blms commented 4 years ago

❓Question

I'm using Formik on Next.js in combination with a library called next-auth. This library uses JWT authentication and a React hook to populate a client-side session variable with some user data.

I've written a function getInitialValues that uses data from the session variable to request additional data from my API. I then want to populate the form's initial values with that data.

It's definitely fetching that data from the API because it's all showing up in my error message debug element. But I can't seem to populate initialValues with that data even though I have enableReinitialize={true} set.

Here is the basic structure of my code:

import { useState } from 'react';
import { useSession } from 'next-auth/client';
import fetch from 'unfetch';
import { Formik } from 'formik';

[…]

const EditProfile = () => {
  const [session] = useSession();

  const [errorMsg, setErrorMsg] = useState('');

  const getInitialValues = async () => {
    const { email } = session.user;
    const url = `/api/user/${email}`;
    const res = await fetch(url, {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' },
    });
    if (res.status === 200) {
      const user = await res.json();
      setErrorMsg(JSON.stringify(user)); //This correctly populates the errorMsg element with the email, firstName, lastName, and affiliation.
      const { firstName, lastName, affiliation } = user;
      return Promise.resolve({
        email, firstName, lastName, affiliation,
      });
    }
    return Promise.resolve({
      error: 'User not found',
    });
  };

[…]

  return (
    <>
      {session && (
        <Formik
          onSubmit={(values, actions) => {
            setTimeout(() => {
              submitHandler(values);
              actions.setSubmitting(false);
            }, 1000);
          }}
          validationSchema={schema}
          initialValues={getInitialValues()}
          enableReinitialize={true}
        >
          {(props) => (
            <Form onSubmit={props.handleSubmit} noValidate>
              {errorMsg ? <p style={{ color: 'red' }}>{errorMsg}</p> : null}
              <Form.Group as={Row} controlId="formPlaintextEmail">
                <Form.Label column lg="4">
                  Email
                </Form.Label>
                <Col>
                  <Form.Control name="email" plaintext readOnly defaultValue={session.user.email} />
                </Col>
              </Form.Group>

              <Form.Group as={Row} controlId="validationFormik01">
                <Form.Label column lg="4">
                  First Name
                </Form.Label>
                <Col>
                  <Form.Control
                    type="text"
                    name="firstName"
                    placeholder="First Name"
                    onChange={props.handleChange}
                    onBlur={props.handleBlur}
                    value={props.values.firstName}
                    defaultValue={props.values.firstName}
                    isValid={props.touched.firstName && !props.errors.firstName}
                    isInvalid={!!props.errors.firstName}
                  />
                  <Form.Control.Feedback type="invalid">
                    {props.errors.firstName}
                  </Form.Control.Feedback>
                </Col>
              </Form.Group>
            </Form>
          )}
        </Formik>
      )}
    </>
  );
};

Am I doing something wrong here? Do I need to manually reset the form somehow once the values are returned from getInitialValues()?

blms commented 4 years ago

Ended up solving this using next.js dynamic routing and getServerSideProps instead of relying on the session for any user data.