React Formik 在 <Formik /> 之外使用 submitForm
Posted
技术标签:
【中文标题】React Formik 在 <Formik /> 之外使用 submitForm【英文标题】:React Formik use submitForm outside <Formik /> 【发布时间】:2018-09-06 13:56:39 【问题描述】:当前行为
<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#issuecomment-317169770,但它总是返回给我Uncaught TypeError: _this.props.onSubmit is not a function
当我尝试console.log(this.form)
有submitForm
功能。
有什么解决办法吗?
- Formik 版本:最新 - 反应版本:v16 - 操作系统:Mac 操作系统
【问题讨论】:
【参考方案1】:仅供想知道通过 React hooks 解决什么问题的人使用:
Formik 2.x,如this 回答中所述
// 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
属性。
TLDR ;如果您提交的组件是 <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 />
【讨论】:
Formik 现在是一个功能组件 这就是为什么 Formik 组件有一个innerRef
属性,如果我是正确的,传递你想要附加的引用:github.com/jaredpalmer/formik/blob/…
在 TypeScript 中使用 useRef
不要忘记设置 FormikValues 类型 useRef<FormikValues>()
简单最好的解决方案
Formik 1.5.x 的最佳解决方案。 Formik 2.x,见ZEE发布的解决方案【参考方案2】:
您可以将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;
【讨论】:
函数如何做到这一点?不太清楚如何使用书籍。 @iwaduarte 你的意思是“钩子”:-)?对我来说,这看起来是一个很好的 useRef 案例 添加了“钩子”版本的答案:) 看起来很糟糕。如果这不是内部方法就好了。 这是一种很老套的做法。我建议你使用来自 formik 的ref
并在你需要的地方使用它【参考方案3】:
这里描述了我找到的最佳解决方案https://***.com/a/53573760
在此复制答案:
在表单中添加“id”属性:id='my-form'
class CustomForm extends Component
render()
return (
<form id='my-form' onSubmit=alert('Form submitted!')>
// Form Inputs go here
</form>
);
然后在表单外的目标按钮的“form”属性中添加相同的Id:
<button form='my-form' type="submit">Outside Button</button>
现在,“外部按钮”按钮将与表单内部完全等效。
注意:IE11 不支持此功能。
【讨论】:
【参考方案4】:我刚刚遇到了同样的问题,并找到了一个非常简单的解决方案希望这会有所帮助:
这个问题可以通过简单的html
来解决。如果您在form
上放置了id
标记,那么您可以使用按钮的form
标记将其定位到按钮。
示例:
<button type="submit" form="form1">
Save
</button>
<form onSubmit=handleSubmit id="form1">
....
</form>
您可以将表单和按钮放置在任何地方,甚至可以分开放置。
然后,此按钮将触发表单提交功能,formik 将捕获该功能并照常继续该过程。 (只要在呈现按钮的同时在屏幕上呈现表单,那么无论表单和按钮位于何处,这都会起作用)
【讨论】:
【参考方案5】:如果您将类组件转换为函数式组件,自定义挂钩 useFormikContext
提供了一种在树的任何位置使用提交的方法:
const values, submitForm = useFormikContext();
PS:这仅适用于那些实际上不需要在 Formik
组件之外调用 submit 的人,因此您可以将 Formik
组件置于更高级别而不是使用 ref在组件树中,并使用自定义钩子useFormikContext
,但如果真的需要从Formik
外部提交,则必须使用useRef
。
<Formik innerRef=formikRef />
https://formik.org/docs/api/useFormikContext
【讨论】:
另外,值得注意的是useFormikContext
仅存在于 Formik 2.x
在树下,ref 允许你使用外部 Formik 组件吗?
我认为首选的方法是使用 withFormik
HoC 包装将提交的组件 --> formik.org/docs/api/withFormik 以便能够调用 useFormikContext。
为什么?如果您的根组件是 Formik,则不需要 withFormik
问题是一个6字的句子。一是“外”。你怎么能认为这是一个正确的答案?【参考方案6】:
在 2021 年,使用 16.13.1,这种方式对我来说满足了几个要求:
提交/重置按钮不能嵌套在<Formik>
元素中。请注意,如果你能做到这一点,那么你应该使用useFormikContext
答案,因为它比我的更简单。 (我的将允许您更改要提交的表单(我有一个应用栏,但用户可以导航到多个表单)。
外部提交/重置按钮必须能够提交和重置 Formik 表单。
外部提交/重置按钮必须显示为禁用状态,直到表单变脏(外部组件必须能够观察 Formik 表单的 dirty
状态。)
这是我想出的:我创建了一个新的上下文提供程序,专门用于保存一些有用的 Formik 东西来链接我的两个外部组件,它们位于应用程序的不同嵌套分支中(全局应用程序栏和其他地方的表单,更深在页面视图中——事实上,我需要提交/重置按钮来适应用户导航到的不同表单,而不仅仅是一个;不仅仅是一个 <Formik>
元素,而是一次只有一个)。
以下示例使用 TypeScript,但如果您只知道 javascript,请忽略冒号后面的内容,在 JS 中也是如此。
您将<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>>
forceUpdate: () => 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 [refresher, setRefresher] = useState<number>(0)
const store: ContextProps =
formikFormRef,
// workaround to allow components to observe the ref changes like formikFormRef.current.dirty
forceUpdate: () => setRefresher(refresher + 1),
return <FormContext.Provider value=store>children</FormContext.Provider>
在呈现<Formik>
元素的组件中,我添加了这一行:
const formikFormRef = useContext(FormContext)
在同一个组件中,我将此属性添加到<Formik>
元素:
innerRef=formikFormRef
在同一个组件中,嵌套在 <Formik>
元素下的第一件事就是 this(重要的是,注意添加了 <FormContextRefreshConduit />
行)。
<Formik
innerRef=formikFormRef
initialValues=initialValues
...
>
( submitForm, isSubmitting, initialValues, values, setErrors, errors, resetForm, dirty ) => (
<Form>
<FormContextRefreshConduit />
...
在包含提交/重置按钮的组件中,我有以下内容。注意formikFormRef
的使用
export const MyAppBar: React.FC<Props> = () =>
const formikFormRef = useContext(FormContext)
const dirty = formikFormRef.current?.dirty
return (
<>
<AppButton
onClick=formikFormRef.current?.resetForm
disabled=!dirty
>
Revert
</AppButton>
<AppButton
onClick=formikFormRef.current?.submitForm
disabled=!dirty
>
Save
</AppButton>
</>
)
ref
对调用 Formik 方法很有用,但通常无法观察到它的 dirty
属性(react 不会为此更改触发重新渲染)。 FormContextRefreshConduit
和 forceUpdate
是一种可行的解决方法。
谢谢,我从其他答案中汲取灵感,想找到一种方法来满足我自己的所有要求。
【讨论】:
对forceUpdate
的补充非常好。这就是innerRef
的问题——它不会触发父组件中的重新渲染。您在使用它来公开 Formik 方法(例如 submitForm
或 resetForm
)与使用它来观察状态(例如 dirty
或 isSubmitting
)之间的区别很明显。很有帮助。【参考方案7】:
如果您使用 withFormik,这对我有用:
const handleSubmitThroughRef = () =>
newFormRef.current.dispatchEvent(
new Event("submit", cancelable: true, bubbles: true )
);
;
只需在您的表单上放置一个常规的 react ref:
<form
ref=newFormRef
onSubmit=handleSubmit
>
【讨论】:
【参考方案8】:找到了罪魁祸首。
Formik 道具上不再有onSubmitCallback
。应该改成onSubmit
【讨论】:
【参考方案9】:你可以试试
const submitForm = ( values, setSubmitting ) =>//...在这里做点什么
【讨论】:
【参考方案10】:另一种简单的方法是使用状态并将 prop 传递给子 formik 组件。在那里你可以使用 useEffect 钩子设置状态。
const ParentComponent = () =>
const [submitFunc, setSubmitFunc] = useState()
return <ChildComponent setSubmitFunc=setSubmitFunc>
const ChildComponent= ( handleSubmit, setSubmitFunc ) =>
useEffect(() =>
if (handleSubmit) setSubmitFunc(() => handleSubmit)
, [handleSubmit, setSubmitFunc])
return <></>
【讨论】:
以上是关于React Formik 在 <Formik /> 之外使用 submitForm的主要内容,如果未能解决你的问题,请参考以下文章
如何在 react-apollo-hooks 和 formik 中使用突变?
React Formik:如何使用自定义 onChange 和 onBlur