You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将按钮选中的预算值同步到React Final Form并提交

React Final Form:点击预设按钮更新输入框值但无法提交的解决方案

我最近在做一个多步骤表单项目,用了React Final Form和Material-UI,想要实现点击$300、$500、$1000这三个按钮,自动把对应数值填入campaignAmount输入框里。现在按钮点了输入框能显示对应值,但提交表单的时候这个值根本没被Final Form收录进去,折腾了好半天,终于找到问题所在了!

问题根源

你现在是用本地状态txtValue来更新输入框显示,但Final Form有自己的状态管理机制,直接改输入框或者本地状态,表单根本不知道这个值变了,自然提交的时候不会带上它。

解决步骤

核心就是要用Final Form提供的form.change()方法来同步更新表单的状态,而不是自己维护本地状态。

  1. 移除无用的本地状态
    先把不需要的txtValue状态删掉,我们不需要它了:

    // 删掉这一行
    // const [txtValue, setTxtValue] = useState({})
    
  2. 修改按钮点击事件处理函数
    利用表单render回调里拿到的form对象,调用form.change()来更新campaignAmount字段的值:

    function schemaTypeSelectionHandle(event, form) {
      const amount = event.target.value;
      // 用form.change同步更新表单状态,转成Number类型适配输入框的number类型
      form.change('campaignAmount', Number(amount));
    }
    
  3. 调整按钮组的点击绑定
    把点击事件直接绑定到每个按钮上,传递form对象给处理函数:

    <ButtonGroup color="primary" aria-label="outlined primary button group">
      <Button value='300' onClick={(e) => schemaTypeSelectionHandle(e, form)}>$300</Button>
      <Button value='500' onClick={(e) => schemaTypeSelectionHandle(e, form)}>$500</Button>
      <Button value='1000' onClick={(e) => schemaTypeSelectionHandle(e, form)}>$1000</Button>
    </ButtonGroup>
    
  4. 顺便修复一个小拼写错误
    你的KeyboardDatePicker里dateFunsUtils拼写错了,应该是dateFnsUtils,不然日期选择器可能会出问题:

    <KeyboardDatePicker
      label="Start Date"
      format="dd-MM-yyyy"
      name="startDate"
      dateFnsUtils={DateFnsUtils} // 修正拼写
      required={true}
      value={new Date()}
    />
    <KeyboardDatePicker
      label="End Date"
      format="dd-MM-yyyy"
      name="endDate"
      dateFnsUtils={DateFnsUtils} // 同样修正这里
      value={new Date(nextyear,nextmonth - 1,nextday)}
      required={true}/>
    
  5. 同步修改外部检测组件的字段名
    把ExternalModificationDetector的name改成campaignAmount,这样它才能正确检测到目标字段的外部修改:

    <ExternalModificationDetector name="campaignAmount">
      {externallyModified => (
        <BooleanDecay value={externallyModified} delay={1000}>
          {highlight => (
            <TextField
              name="campaignAmount"
              id="campaignAmount"
              component="input"
              type="number"
              placeholder="Total"
              style={{ transition: 'background 500ms ease-in-out', background: highlight ? 'yellow' : 'none' }}
            />
          )}
        </BooleanDecay>
      )}
    </ExternalModificationDetector>
    

修改后的完整核心代码

import React from 'react';
import Wizard from '../wrappers/Wizard'
import { Form, Field } from 'react-final-form'
import ExternalModificationDetector from '../wrappers/ExternalModificationDetector';
import BooleanDecay from '../wrappers/BooleanDecay'
import { Typography, Paper, Grid, Button, ButtonGroup, Box, CssBaseline, } from '@material-ui/core';
import DateFnsUtils from '@date-io/date-fns';
import { TextField, Radios, KeyboardDatePicker, } from 'mui-rff';

const onSubmit = async values => {
  const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  await sleep(300);
  window.alert(JSON.stringify(values, 0, 2));
};

// MAIN FORM AREA
export default function BuildAdStepper(props: MyFormProps) {
  const today = new Date();
  const dateTimeFormat = new Intl.DateTimeFormat('en', { year: 'numeric', month: '2-digit', day: '2-digit' })
  const [{ value: month },,{ value: day },,{ value: year }] = dateTimeFormat.formatToParts(today)
  const nmonth = new Date(today.getFullYear(), today.getMonth()+1, day);
  const [{ value: nextmonth },,{ value: nextday },,{ value: nextyear }] = dateTimeFormat.formatToParts(nmonth)

  function schemaTypeSelectionHandle(event, form) {
    const amount = event.target.value;
    form.change('campaignAmount', Number(amount));
  }

  const calculator = createDecorator({
    field: /day\[\d\]/, // when a field matching this pattern changes...
    updates: {
      // ...update the total to the result of this function
      total: (ignoredValue, allValues) =>
        (allValues.day || []).reduce((sum, value) => sum + Number(value || 0), 0)
    }
  })

  return (
    <div style={{ padding: 16, margin: 'auto', maxWidth: 600 }}>
      <CssBaseline />
      <Typography variant="h5" align="center" component="h2" gutterBottom>
        Create your campaign
      </Typography>
      <Paper style={{ padding: 16 }}>
        <Form
          onSubmit={onSubmit}
          decorators={[calculator]}
          render={({ handleSubmit, form, submitting, pristine, values }) => (
            <Wizard
              initialValues={{promoting:"business", startDate:`${day}-${month}-${year }`,endDate:`${nextday}-${nextmonth}-${nextyear }`}}
              onSubmit={onSubmit}
            >
              <Wizard.Page>
                <Typography variant="h5">What are you Advertising</Typography>
                <Box m={2}/>
                <Radios
                  name="promoting"
                  formControlProps={{ margin: 'none' }}
                  radioGroupProps={{ row: true }}
                  data={[
                    { label: 'Business', value: 'business' },
                    { label: 'Community Project', value: 'community' },
                    { label: 'Event', value: 'event' },
                  ]}
                />
                <Box m={2}/>
              </Wizard.Page>
              <Wizard.Page>
                <Typography variant="h5">Name Your Campaign</Typography>
                <Box m={2}/>
                <TextField
                  name="campaignName"
                  margin="none"
                  fullWidth
                  variant="outlined"
                  required={true}
                />
                <Box m={2}/>
              </Wizard.Page>
              <Wizard.Page>
                <Typography variant="h5">Set your schedule and budget</Typography>
                <Box m={2} />
                <KeyboardDatePicker
                  label="Start Date"
                  format="dd-MM-yyyy"
                  name="startDate"
                  dateFnsUtils={DateFnsUtils}
                  required={true}
                  value={new Date()}
                />
                <Box m={2} />
                <KeyboardDatePicker
                  label="End Date"
                  format="dd-MM-yyyy"
                  name="endDate"
                  dateFnsUtils={DateFnsUtils}
                  value={new Date(nextyear,nextmonth - 1,nextday)}
                  required={true}/>
                <Box m={2} />
                <Typography variant="h5">Your budget will be evenly distributed between start and end dates.</Typography>
                <Grid container>
                  <Grid item lg={6}>
                    <ButtonGroup color="primary" aria-label="outlined primary button group">
                      <Button value='300' onClick={(e) => schemaTypeSelectionHandle(e, form)}>$300</Button>
                      <Button value='500' onClick={(e) => schemaTypeSelectionHandle(e, form)}>$500</Button>
                      <Button value='1000' onClick={(e) => schemaTypeSelectionHandle(e, form)}>$1000</Button>
                    </ButtonGroup>
                  </Grid>
                  <Grid item lg={3}>
                    <ExternalModificationDetector name="campaignAmount">
                      {externallyModified => (
                        <BooleanDecay value={externallyModified} delay={1000}>
                          {highlight => (
                            <TextField
                              name="campaignAmount"
                              id="campaignAmount"
                              component="input"
                              type="number"
                              placeholder="Total"
                              style={{ transition: 'background 500ms ease-in-out', background: highlight ? 'yellow' : 'none' }}
                            />
                          )}
                        </BooleanDecay>
                      )}
                    </ExternalModificationDetector>
                  </Grid>
                </Grid>
              </Wizard.Page>
            </Wizard>
          )}
        />
      </Paper>
    </div>
  );
}

为什么这样能行?

form.change()是Final Form提供的官方方法,专门用来手动更新表单字段的值,调用它之后,表单的状态会同步更新,输入框也会自动显示最新的值,而且提交的时候这个值就会被包含在表单数据里了。同时修正后的ExternalModificationDetector也能正确检测到字段的外部修改,高亮提示效果也能正常工作。

内容的提问来源于stack exchange,提问作者RussellHarrower

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 19:48:15