diff --git a/src/Field.js b/src/Field.js
index baab6f01..4e7706e8 100644
--- a/src/Field.js
+++ b/src/Field.js
@@ -16,6 +16,7 @@ const Field = ({
   formatOnBlur,
   initialValue,
   isEqual,
+  keepFieldStateOnUnmount,
   multiple,
   name,
   parse,
@@ -38,6 +39,7 @@ const Field = ({
     formatOnBlur,
     initialValue,
     isEqual,
+    keepFieldStateOnUnmount,
     multiple,
     parse,
     subscription,
diff --git a/src/ReactFinalForm.test.js b/src/ReactFinalForm.test.js
index 5e38ac37..0de19009 100644
--- a/src/ReactFinalForm.test.js
+++ b/src/ReactFinalForm.test.js
@@ -912,7 +912,8 @@ describe('ReactFinalForm', () => {
   it('should allow an alternative form api to be passed in', () => {
     const onSubmit = jest.fn()
     const form = createForm({ onSubmit: onSubmit })
-    const formMock = jest.spyOn(form, 'registerField')
+    const registerFieldSpy = jest.spyOn(form, 'registerField')
+    const subscribeToFieldStateSpy = jest.spyOn(form, 'subscribeToFieldState')
     render(
       <Form form={form}>
         {({ handleSubmit }) => (
@@ -922,14 +923,199 @@ describe('ReactFinalForm', () => {
         )}
       </Form>
     )
-    expect(formMock).toHaveBeenCalled()
 
-    // called once on first render to get initial state, and then again to subscribe
-    expect(formMock).toHaveBeenCalledTimes(2)
-    expect(formMock.mock.calls[0][0]).toBe('name')
-    expect(formMock.mock.calls[0][2].active).toBe(true) // default subscription
-    expect(formMock.mock.calls[1][0]).toBe('name')
-    expect(formMock.mock.calls[1][2].active).toBe(true) // default subscription
+    // called once on first render to register only once
+    expect(registerFieldSpy).toHaveBeenCalledTimes(1)
+    expect(registerFieldSpy.mock.calls[0][0]).toBe('name')
+    expect(registerFieldSpy.mock.calls[0][1]).toBe(undefined) // no initial callback
+    expect(registerFieldSpy.mock.calls[0][2]).toBe(undefined) // no subscription
+
+    // subscribe to field state once
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(1)
+    expect(subscribeToFieldStateSpy.mock.calls[0][0]).toBe('name')
+    expect(subscribeToFieldStateSpy.mock.calls[0][2].active).toBe(true) // default subscription
+  })
+
+  it('should keep field states when field is hidden with keepFieldStateOnUnmount option', () => {
+    const { getByTestId, getByText } = render(
+      <Toggle>
+        {hidden => (
+          <Form initialValues={{ name: 'erikras' }} onSubmit={onSubmitMock}>
+            {({ handleSubmit }) => (
+              <form onSubmit={handleSubmit}>
+                {!hidden && (
+                  <Field
+                    keepFieldStateOnUnmount
+                    name="name"
+                    validate={v =>
+                      v.toLowerCase() !== v ? 'SHOULD BE LOWERCASE' : undefined
+                    }
+                  >
+                    {({ input, meta }) => (
+                      <>
+                        <input {...input} data-testid="name" />
+                        <span data-testid="error">{meta.error}</span>
+                      </>
+                    )}
+                  </Field>
+                )}
+              </form>
+            )}
+          </Form>
+        )}
+      </Toggle>
+    )
+
+    const nameField = getByTestId('name')
+    const errorElem = getByTestId('error')
+    expect(nameField).toHaveValue('erikras')
+    expect(errorElem).not.toHaveTextContent('SHOULD BE LOWERCASE')
+
+    fireEvent.change(nameField, { target: { value: 'ERIKRAS' } })
+
+    expect(nameField).toHaveValue('ERIKRAS')
+    expect(errorElem).toHaveTextContent('SHOULD BE LOWERCASE')
+
+    const toggleButton = getByText('Toggle')
+    // hide
+    fireEvent.click(toggleButton)
+    expect(nameField).not.toBeInTheDocument()
+
+    // show
+    fireEvent.click(toggleButton)
+    expect(nameField).toHaveValue('ERIKRAS')
+    expect(errorElem).toHaveTextContent('SHOULD BE LOWERCASE')
+  })
+
+  it('should not re-register when hidden field becomes visible again with keepFieldStateOnUnmount option', () => {
+    const onSubmit = jest.fn()
+    const form = createForm({ onSubmit: onSubmit })
+    const registerFieldSpy = jest.spyOn(form, 'registerField')
+    const subscribeToFieldStateSpy = jest.spyOn(form, 'subscribeToFieldState')
+
+    const { getByTestId, getByText } = render(
+      <Toggle>
+        {hidden => (
+          <Form form={form} onSubmit={onSubmitMock}>
+            {({ handleSubmit }) => (
+              <form onSubmit={handleSubmit}>
+                {!hidden && (
+                  <Field
+                    component="input"
+                    data-testid="name"
+                    keepFieldStateOnUnmount
+                    name="name"
+                  />
+                )}
+              </form>
+            )}
+          </Form>
+        )}
+      </Toggle>
+    )
+
+    const toggleButton = getByText('Toggle')
+    expect(registerFieldSpy).toHaveBeenCalledTimes(1)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(1)
+
+    // hide
+    fireEvent.click(toggleButton)
+    expect(registerFieldSpy).toHaveBeenCalledTimes(1)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(1)
+
+    // show
+    fireEvent.click(toggleButton)
+    expect(registerFieldSpy).toHaveBeenCalledTimes(1)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(2)
+  })
+
+  it('should re-register with the name prop change', () => {
+    const onSubmit = jest.fn()
+    const form = createForm({ onSubmit: onSubmit })
+    const registerFieldSpy = jest.spyOn(form, 'registerField')
+    const subscribeToFieldStateSpy = jest.spyOn(form, 'subscribeToFieldState')
+
+    const { getByTestId, getByText } = render(
+      <Toggle>
+        {isCat => (
+          <Form form={form} onSubmit={onSubmitMock}>
+            {({ handleSubmit }) => (
+              <form onSubmit={handleSubmit}>
+                <Field
+                  component="input"
+                  data-testid="name"
+                  name={isCat ? 'cat' : 'dog'}
+                />
+              </form>
+            )}
+          </Form>
+        )}
+      </Toggle>
+    )
+
+    const nameField = getByTestId('name')
+    const toggleButton = getByText('Toggle')
+    expect(registerFieldSpy).toHaveBeenCalledTimes(1)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(1)
+
+    // change to the input field shouldn't trigger re-register
+    fireEvent.change(nameField, { target: { value: 'Jon' } })
+    expect(registerFieldSpy).toHaveBeenCalledTimes(1)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(1)
+
+    fireEvent.click(toggleButton)
+    expect(registerFieldSpy).toHaveBeenCalledTimes(2)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(2)
+
+    fireEvent.click(toggleButton)
+    expect(registerFieldSpy).toHaveBeenCalledTimes(3)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(3)
+  })
+
+  it('should re-register with the name prop change with keepFieldStateOnUnmount', () => {
+    const onSubmit = jest.fn()
+    const form = createForm({ onSubmit: onSubmit })
+    const registerFieldSpy = jest.spyOn(form, 'registerField')
+    const subscribeToFieldStateSpy = jest.spyOn(form, 'subscribeToFieldState')
+
+    const { getByTestId, getByText } = render(
+      <Toggle>
+        {isCat => (
+          <Form form={form} onSubmit={onSubmitMock}>
+            {({ handleSubmit }) => (
+              <form onSubmit={handleSubmit}>
+                <Field
+                  component="input"
+                  data-testid="name"
+                  name={isCat ? 'cat' : 'dog'}
+                  keepFieldStateOnUnmount
+                />
+              </form>
+            )}
+          </Form>
+        )}
+      </Toggle>
+    )
+
+    const nameField = getByTestId('name')
+    const toggleButton = getByText('Toggle')
+    expect(registerFieldSpy).toHaveBeenCalledTimes(1)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(1)
+
+    // change to the input field shouldn't trigger re-register
+    fireEvent.change(nameField, { target: { value: 'Jon' } })
+    expect(registerFieldSpy).toHaveBeenCalledTimes(1)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(1)
+
+    fireEvent.click(toggleButton)
+    expect(registerFieldSpy).toHaveBeenCalledTimes(2)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(2)
+
+    // this should change the name back to cat (or dog, whatever toggle toggles the toggle)
+    // since the states weren't removed, registration should not happen again, but subscription to field will
+    fireEvent.click(toggleButton)
+    expect(registerFieldSpy).toHaveBeenCalledTimes(2)
+    expect(subscribeToFieldStateSpy).toHaveBeenCalledTimes(3)
   })
 
   it('should not destroy on unregister on initial unregister', () => {
diff --git a/src/types.js.flow b/src/types.js.flow
index 5784a865..a06d7143 100644
--- a/src/types.js.flow
+++ b/src/types.js.flow
@@ -94,6 +94,7 @@ export type UseFieldAutoConfig = {
   formatOnBlur?: boolean,
   initialValue?: any,
   isEqual?: (a: any, b: any) => boolean,
+  keepFieldStateOnUnmount?: boolean,
   multiple?: boolean,
   parse?: (value: any, name: string) => any,
   type?: string,
diff --git a/src/useField.js b/src/useField.js
index 48a52499..7034441c 100644
--- a/src/useField.js
+++ b/src/useField.js
@@ -39,6 +39,7 @@ function useField<FormValues: FormValuesShape>(
     format = defaultFormat,
     formatOnBlur,
     initialValue,
+    keepFieldStateOnUnmount = false,
     multiple,
     parse = defaultParse,
     subscription = all,
@@ -50,13 +51,13 @@ function useField<FormValues: FormValuesShape>(
 
   const configRef = useLatest(config)
 
-  const register = (callback: FieldState => void) =>
+  const register = () =>
     // avoid using `state` const in any closures created inside `register`
     // because they would refer `state` from current execution context
     // whereas actual `state` would defined in the subsequent `useField` hook
     // execution
     // (that would be caused by `setState` call performed in `register` callback)
-    form.registerField(name, callback, subscription, {
+    form.registerField(name, undefined, undefined, {
       afterSubmit,
       beforeSubmit: () => {
         const {
@@ -84,49 +85,61 @@ function useField<FormValues: FormValuesShape>(
       validateFields
     })
 
-  const firstRender = React.useRef(true)
+  const firstRenderRef = React.useRef(true)
+  const unregisterRef = React.useRef()
+
+  const registerAndGetInitialState: () => FieldState = () => {
+    let initState
+    if (!keepFieldStateOnUnmount) {
+      unregisterRef.current && unregisterRef.current()
+      unregisterRef.current = undefined
+    } else {
+      initState = form.getFieldState(name)
+    }
+
+    // if no initial state, register!
+    if (!initState) {
+      const unregisterFn = register()
+      // only set unregister function when keepFieldStateOnUnmount option is false
+      if (keepFieldStateOnUnmount === false) {
+        unregisterRef.current = unregisterFn
+      }
+      initState = form.getFieldState(name)
+    }
 
-  // synchronously register and unregister to query field state for our subscription on first render
-  const [state, setState] = React.useState<FieldState>((): FieldState => {
-    let initialState: FieldState = {}
+    return initState || {}
+  }
 
-    // temporarily disable destroyOnUnregister
-    const destroyOnUnregister = form.destroyOnUnregister
-    form.destroyOnUnregister = false
+  // register on first render
+  // this will initially check for existing field state from the form before trying to register
+  const [state, setState] = React.useState<FieldState>(
+    registerAndGetInitialState
+  )
 
-    register(state => {
-      initialState = state
-    })()
+  React.useEffect(
+    () => {
+      // make sure this doesn't get triggered on first render
+      if (firstRenderRef.current === false) {
+        unregisterRef.current && unregisterRef.current()
+        setState(registerAndGetInitialState())
+      }
 
-    // return destroyOnUnregister to its original value
-    form.destroyOnUnregister = destroyOnUnregister
+      const unsubscribeFieldState = form.subscribeToFieldState(
+        name,
+        setState,
+        subscription
+      )
 
-    return initialState
-  })
+      firstRenderRef.current = false
 
-  React.useEffect(
-    () =>
-      register(state => {
-        if (firstRender.current) {
-          firstRender.current = false
-        } else {
-          setState(state)
-        }
-      }),
+      return () => {
+        unsubscribeFieldState()
+        unregisterRef.current && unregisterRef.current()
+        unregisterRef.current = undefined
+      }
+    },
     // eslint-disable-next-line react-hooks/exhaustive-deps
-    [
-      name,
-      data,
-      defaultValue,
-      // If we want to allow inline fat-arrow field-level validation functions, we
-      // cannot reregister field every time validate function !==.
-      // validate,
-      initialValue
-      // The validateFields array is often passed as validateFields={[]}, creating
-      // a !== new array every time. If it needs to be changed, a rerender/reregister
-      // can be forced by changing the key prop
-      // validateFields
-    ]
+    [defaultValue, initialValue, name]
   )
 
   const handlers = {