Реагировать Formik использовать submitForm за пределами <Formik />
Текущее поведение
<Formik
isInitialValid
initialValues={{ first_name: 'Test', email: 'test@mail.com' }}
validate={validate}
ref={node => (this.form = node)}
onSubmitCallback={this.onSubmitCallback}
render={formProps => {
const fieldProps = { formProps, margin: 'normal', fullWidth: true, };
const {values} = formProps;
return (
<Fragment>
<form noValidate>
<TextField
{...fieldProps}
required
autoFocus
value={values.first_name}
type="text"
name="first_name"
/>
<TextField
{...fieldProps}
name="last_name"
type="text"
/>
<TextField
{...fieldProps}
required
name="email"
type="email"
value={values.email}
/>
</form>
<Button onClick={this.onClick}>Login</Button>
</Fragment>
);
}}
/>
Я пробую это решение https://github.com/jaredpalmer/formik/issues/73, но оно всегда возвращает меня Uncaught TypeError: _this.props.onSubmit is not a function
Когда я пытался console.log(this.form)
есть submitForm
функция.
Любое решение, ребята?
- Версия Formik: последняя - Версия React: v16 - ОС: Mac OS
13 ответов
Нашел виновника.
Больше нет onSubmitCallback
на формике реквизит. Следует изменить его на onSubmit
Просто для тех, кто задается вопросом, каково решение с помощью хуков React:
Formik 2.x, как описано в этом ответе
// import this in the related component
import { useFormikContext } from 'formik';
// Then inside the component body
const { submitForm } = useFormikContext();
const handleSubmit = () => {
submitForm();
}
Имейте в виду, что решение работает только для компонентов внутри компонента Formik, поскольку оно использует контекстный API. Если по какой-то причине вы хотите вручную отправить из внешнего компонента или из компонента, из которого фактически используется Formik, вы все равно можете использоватьinnerRef
опора
TL; DR; Этот контекстный ответ работает как шарм, если компонент, который вы отправляете, является дочерним по отношению к<Formik>
или withFormik()
компонент, в противном случае используйте innerRef
ответ ниже.
Formik 1.5.x+
// Attach this to your <Formik>
const formRef = useRef()
const handleSubmit = () => {
if (formRef.current) {
formRef.current.handleSubmit()
}
}
// Render
<Formik innerRef={formRef} />
Вы можете связать formikProps.submitForm
(Программная отправка Formik) родительскому компоненту, а затем инициировать отправку от родителя:
import React from 'react';
import { Formik } from 'formik';
class MyForm extends React.Component {
render() {
const { bindSubmitForm } = this.props;
return (
<Formik
initialValues={{ a: '' }}
onSubmit={(values, { setSubmitting }) => {
console.log({ values });
setSubmitting(false);
}}
>
{(formikProps) => {
const { values, handleChange, handleBlur, handleSubmit } = formikProps;
// bind the submission handler remotely
bindSubmitForm(formikProps.submitForm);
return (
<form noValidate onSubmit={handleSubmit}>
<input type="text" name="a" value={values.a} onChange={handleChange} onBlur={handleBlur} />
</form>
)
}}
</Formik>
)
}
}
class MyApp extends React.Component {
// will hold access to formikProps.submitForm, to trigger form submission outside of the form
submitMyForm = null;
handleSubmitMyForm = (e) => {
if (this.submitMyForm) {
this.submitMyForm(e);
}
};
bindSubmitForm = (submitForm) => {
this.submitMyForm = submitForm;
};
render() {
return (
<div>
<button onClick={this.handleSubmitMyForm}>Submit from outside</button>
<MyForm bindSubmitForm={this.bindSubmitForm} />
</div>
)
}
}
export default MyApp;
У меня была такая же проблема, и я нашел очень простое решение, надеюсь, это поможет:
Проблему можно решить простым
html
. Если поставить
id
тег на вашем, тогда вы можете настроить его с помощью кнопки, используя кнопку
form
тег.
пример:
<button type="submit" form="form1">
Save
</button>
<form onSubmit={handleSubmit} id="form1">
....
</form>
Вы можете разместить форму и кнопку где угодно, даже отдельно.
Эта кнопка затем активирует функцию отправки форм, и formik зафиксирует, что продолжит процесс как обычно. (пока форма отображается на экране, пока отображается кнопка, это будет работать независимо от того, где находятся форма и кнопка)
Лучшее решение, которое я нашел, описано здесь /questions/34019641/kak-otpravit-formu-s-knopki-vne-etogo-komponenta-v-react/55216516#55216516
Копируем ответ сюда:
Добавьте в форму атрибут id: id = 'my-form'
class CustomForm extends Component {
render() {
return (
<form id='my-form' onSubmit={alert('Form submitted!')}>
// Form Inputs go here
</form>
);
}
}
Затем добавьте тот же идентификатор в атрибут "form" целевой кнопки вне формы:
<button form='my-form' type="submit">Outside Button</button>
Теперь кнопка «Внешняя кнопка» будет абсолютно такой же, как если бы она находилась внутри формы.
Примечание. Это не поддерживается IE11.
В 2021 году этот способ сработал для меня, чтобы удовлетворить несколько требований:
- Кнопки отправки / сброса не могут быть вложены в элемент. Обратите внимание: если вы можете это сделать, вам следует использовать
useFormikContext
ответ, потому что он проще моего. (Моя позволит вам изменить, какая форма отправляется (у меня одна панель приложения, но несколько форм, к которым пользователь может перейти). - Внешние кнопки отправки / сброса должны иметь возможность отправлять и сбрасывать форму Formik.
- Внешние кнопки отправки / сброса должны отображаться отключенными до тех пор, пока форма не станет грязной (внешний компонент должен иметь возможность наблюдать за состоянием формы Formik).
Вот что я придумал: я создал новый поставщик контекста, предназначенный для хранения некоторых полезных материалов Formik, чтобы связать два моих внешних компонента, которые находятся в разных вложенных ветвях приложения (глобальная панель приложения и форма где-то еще, глубже на странице view - на самом деле мне нужны кнопки отправки / сброса, чтобы адаптироваться к различным формам, к которым пользователь перешел, а не только к одному; не только к одному элементу, а только по одному за раз).
Вы размещаете
<FormContextProvider>
достаточно высока в вашем приложении, чтобы обертывать оба разрозненных компонента, которым необходим доступ к материалам Formik. Упрощенный пример:
<FormContextProvider>
<MyAppBar />
<MyPageWithAForm />
</FormContextProvider>
Вот FormContextProvider:
import React, { MutableRefObject, useRef, useState } from 'react'
import { FormikProps, FormikValues } from 'formik'
export interface ContextProps {
formikFormRef: MutableRefObject<FormikProps<FormikValues>>
dirty: boolean
setDirty: (value: boolean) => void
}
/**
* Used to connect up buttons in the AppBar to a Formik form elsewhere in the app
*/
export const FormContext = React.createContext<Partial<ContextProps>>({})
// https://github.com/deeppatel234/react-context-devtool
FormContext.displayName = 'FormContext'
interface ProviderProps {}
export const FormContextProvider: React.FC<ProviderProps> = ({ children }) => {
// Note, can't add specific TS form values to useRef here because the form will change from page to page.
const formikFormRef = useRef<FormikProps<FormikValues>>(null)
const [dirty, setDirty] = useState<boolean>(false)
const store: ContextProps = {
formikFormRef,
dirty,
setDirty,
}
return <FormContext.Provider value={store}>{children}</FormContext.Provider>
}
В компоненте, который отображает элемент, я добавляю эту строку:
const { formikFormRef, setDirty } = useContext(FormContext)
В том же компоненте я добавляю к элементу этот атрибут:
innerRef={formikFormRef}
В этом же компоненте первое, что вложено в
<Formik>
этот элемент (важно, обратите внимание на добавление
setDirty(dirty)
линия).
Обновление: к сожалению, мы не можем позвонить
setDirty
здесь; мы получим ошибку о попытке установить в контексте при рендеринге :(
<Formik
innerRef={formikFormRef}
initialValues={initialValues}
...
>
{({ submitForm, isSubmitting, initialValues, values, setErrors, errors, resetForm, dirty }) => {
setDirty(dirty)
return (
<Form>
...
В моем компоненте, который содержит кнопки отправки / сброса, у меня есть следующее. Обратите внимание на использование
formikFormRef
и
export const MyAppBar: React.FC<Props> = ({}) => {
const { formikFormRef, dirty } = useContext(FormContext)
return (
<>
<AppButton
onClick={formikFormRef.current?.resetForm}
disabled={!dirty}
>
Revert
</AppButton>
<AppButton
onClick={formikFormRef.current?.submitForm}
disabled={!dirty}
>
Save
</AppButton>
</>
)
}
В
ref
полезен для вызова методов Formik, но не может быть отслежен для его свойства (response не запускает повторную визуализацию для этого изменения). Вот почему
dirty
свойство хранится с использованием
useState
крюк.
Спасибо, я черпал вдохновение из других ответов, чтобы найти способ удовлетворить все мои собственные требования.
Вам не нужно использовать ref, кастомный хук useFormikContext
предоставить способ использовать submit в любом месте дерева, при одном условии, что корневой компонент должен быть компонентом formik:
const { values, submitForm } = useFormikContext();
Если вы используете withFormik, у меня это сработало:
const handleSubmitThroughRef = () => {
newFormRef.current.dispatchEvent(
new Event("submit", { cancelable: true, bubbles: true })
);
};
Просто поместите в форму регулярную ссылку на реакцию:
<form
ref={newFormRef}
onSubmit={handleSubmit}
>
Я построил пример на основе хуков.
https://codesandbox.io/s/formik-submit-button-outside-form-d825u
Можешь попробовать
const submitForm = ({values, setSubmitting}) => {// ... здесь что-то делаем}
<Formik onSubmit = {(values, {setSubmitting}) => submitForm({values, setSubmitting})> {() => (// ... здесь что-то делаем)}
Другой простой подход - использоватьState и передать свойство дочернему компоненту formik. Там вы можете установитьState с помощью хука useEffect.
const ParentComponent = () => {
const [submitFunc, setSubmitFunc] = useState()
return <ChildComponent setSubmitFunc={setSubmitFunc}>
}
const ChildComponent= ({ handleSubmit, setSubmitFunc }) => {
useEffect(() => {
if (handleSubmit) setSubmitFunc(() => handleSubmit)
}, [handleSubmit, setSubmitFunc])
return <></>
}
Я реализовал это в компоненте класса React шаг за шагом:
1 — я объявил переменную «ref», чтобы сохранить ссылку на объект формы (useRef действителен только в функциональных компонентах, поэтому я закодировал, как показано ниже, с помощью функции React.createRef())
constructor(props) {
super(props);
this.visitFormRef = React.createRef();
}
2 - В формах formik есть функция "innerRef", поэтому я присвоил ей переменную ref выше:
<Formik
initialValues={initialValues}
onSubmit={(values) => onSubmit(values)}
validationSchema={validationSchema}
enableReinitialize={true}
innerRef={this.visitFormRef} //<<--here
>
3- Чтобы вызвать событие отправки формы, откуда-то из формы я объявил функцию ниже:
triggerFormSubmit = () => {
if (this.visitFormRef.current)
this.visitFormRef.current.handleSubmit();
}
4- И, наконец, я вызвал функцию выше с внешней кнопки:
<Button onClick={() => this.triggerFormSubmit()} />
Обратите внимание, чтобы не перепутать: функция onSubmit(values), которая назначена форме formik, все еще существует и получает значения from. Мы только что активировали его с помощью внешней кнопки.
Вот как я добился своего, используя ref для имитации нажатия внутренней скрытой кнопки отправки, когда пользователь нажимает внешнюю кнопку отправки.
const hiddenInnerSubmitFormRef = useRef(null);
const handleExternalButtonClick = () => {
hiddenInnerSubmitFormRef.current.click();
}
return (
<>
{/* External Button */}
<button onClick={handleExternalButtonClick}>
External Submit
</button>
<Formik onSubmit={values => console.log(values)}>
{() => (
<Form>
{/* Hide Button /*}
<button type="submit" ref={hiddenInnerSubmitFormRef} className="hidden">
Submit
</button>
</Form>
)}
</Formik>
</>
)