Formik onSubmit错误:无法在已卸载的组件上执行React状态更新

时间:2019-12-04 14:07:54

标签: reactjs formik

尝试提交Formik表单时收到此错误。

  

警告:无法在已卸载的组件上执行React状态更新。   这是空操作,但它表明应用程序中发生内存泄漏。   要修复,请取消useEffect中的所有订阅和异步任务   清理功能。       在Formik中(在layout.tsx:113处)       在div中(由ForwardRef(CardContent)创建)       在ForwardRef(CardContent)中(由WithStyles(ForwardRef(CardContent)创建))       在WithStyles(ForwardRef(CardContent))中(由Context.Consumer创建)       在StyledComponent中(由Styled(WithStyles(ForwardRef(CardContent))创建))       在Styled(WithStyles(ForwardRef(CardContent)))中(由CardContent创建)       在CardContent中(由Context.Consumer创建)       在StyledComponent中(由Styled(CardContent)创建)       在Styled(CardContent)中(在layout.tsx:112处)       在div中(由ForwardRef(Paper)创建)       在ForwardRef(Paper)中(由WithStyles(ForwardRef(Paper)创建))       在WithStyles(ForwardRef(Paper))中(由ForwardRef(Card)创建)       在ForwardRef(Card)中(由WithStyles(ForwardRef(Card)创建))       在WithStyles(ForwardRef(Card))中(由Context.Consumer创建)       在StyledComponent中(由Styled(WithStyles(ForwardRef(Card))创建))       在Styled(WithStyles(ForwardRef(Card)))中(由Card创建)       在Card中(在layout.tsx:111处)

函数onSubmit没有async调用:

<Formik
  ...
  onSubmit={(values, { setSubmitting }): void => {
    setTimeout(() => {
      // convert daysOfWeek to Weekday[]
      const daysOfWeek = values.daysOfWeek
        .filter(day => day.checked)
        .map(day => day.dayOfWeek)
      const edited: EditTask = { ...task, ...values, daysOfWeek }
      onEditTask(edited)
      setSubmitting(false)
    })
  }}
>

onEditTask回调仅更新TaskItems的列表。

完整的TaskItem组件如下所示。 '@web/core...'组件是我自己的Material UI组件包装。

TaskItem.tsx

import React, { ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
import Grid from '@material-ui/core/Grid'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import CloseIcon from '@material-ui/icons/Close'
import Typography from '@web/core/components/Typography'
import MenuButton from '@web/core/components/MenuButton'
import MenuItem from '@web/core/components/MenuItem'
import TextField from '@web/core/components/TextField'
import Checkbox from '@web/core/components/Checkbox'
import Button from '@web/core/components/Button'
import Card from '@web/core/components/Card'
import EditIcon from '@web/core/theme/icons/Edit'
import { TaskSchedule, Weekday } from 'stores/goals/models'
import {
  EditTask,
  getDistinctTaskSchedulesSorted,
} from 'components/CitizenDetails/DietRules/FormDrawer'
import { TaskItemViewMode } from 'components/CitizenDetails/DietRules/TaskItem'
import { Formik, FieldArray, Form } from 'formik'
import uuid from 'uuid/v4'
import * as Yup from 'yup'
import TaskOverview from '../TaskOverview'
import { DeleteIcon, DeleteMenuItemText, EditCardContent, RemoveScheduleButton } from './styled'

const taskValidationSchema = Yup.object().shape({
  subject: Yup.string().required('Required'),
  description: Yup.string().nullable(),
  daysOfWeek: Yup.array()
    .min(1, 'Min 1 day')
    .required('Required'),
  taskSchedules: Yup.array()
    .ensure()
    .min(0, 'Min 0 elements'),
})

interface Props {
  viewMode: TaskItemViewMode
  task: EditTask
  onChangeViewMode: (viewMode: TaskItemViewMode) => void
  onDeleteTask: () => void
  onEditTask: (task: EditTask) => void
  disableMenu?: boolean
}

export interface CheckedWeekday {
  checked: boolean
  dayOfWeek: Weekday
}

function mapToCheckedWeekday(weekday: Weekday, checked: boolean): CheckedWeekday {
  return {
    dayOfWeek: weekday,
    checked,
  }
}

export default function TaskItem({
  viewMode,
  task,
  onChangeViewMode,
  onDeleteTask,
  onEditTask,
  disableMenu,
}: Props): ReactElement {
  const [t] = useTranslation()

  type TaskFormValues = Pick<EditTask, 'subject' | 'description'> & {
    daysOfWeek: CheckedWeekday[]
    taskSchedules: TaskSchedule[]
  }

  const initialValues: TaskFormValues = {
    ...task,
    daysOfWeek: Object.values(Weekday).map(day =>
      mapToCheckedWeekday(day, task.daysOfWeek.includes(day))
    ),
    taskSchedules: getDistinctTaskSchedulesSorted(task.taskSchedules),
  }

  return (
    <>
      {viewMode === TaskItemViewMode.View ? (
        <Grid container justify="space-between" alignItems="center">
          <Grid item>
            <TaskOverview task={task} />
          </Grid>
          <Grid item>
            {!disableMenu && (
              <MenuButton id="diet-task-overview-menu-button">
                <MenuItem onClick={(): void => onChangeViewMode(TaskItemViewMode.Edit)}>
                  <ListItemIcon>
                    <EditIcon />
                  </ListItemIcon>
                  <ListItemText primary={t('edit')} />
                </MenuItem>
                <MenuItem key="diet-task-overview-menu-remove-button" onClick={onDeleteTask}>
                  <ListItemIcon>
                    <DeleteIcon />
                  </ListItemIcon>
                  <DeleteMenuItemText primary={t('remove')} />
                </MenuItem>
              </MenuButton>
            )}
          </Grid>
        </Grid>
      ) : (
        <Card>
          <EditCardContent>
            <Formik
              initialValues={initialValues}
              validationSchema={taskValidationSchema}
              onSubmit={(values, { setSubmitting }): void => {
                setTimeout(() => {
                  // convert daysOfWeek to Weekday[]
                  const daysOfWeek = values.daysOfWeek
                    .filter(day => day.checked)
                    .map(day => day.dayOfWeek)
                  const edited: EditTask = { ...task, ...values, daysOfWeek }
                  onEditTask(edited)
                  setSubmitting(false)
                })
              }}
            >
              {({
                values,
                errors,
                isValid,
                touched,
                dirty,
                handleChange,
                handleBlur,
                handleSubmit,
                handleReset,
                isSubmitting,
                validateForm,
              }): ReactElement => (
                <Form>
                  <Grid container direction="column" spacing={3}>
                    <Grid item>
                      <Typography variant="h6">{t('goals:diet.task.title')}</Typography>
                    </Grid>
                    <Grid item>
                      <TextField
                        id={`task-${task.uuid}-subject`}
                        label={t('title')}
                        name="subject"
                        onChange={handleChange}
                        onBlur={handleBlur}
                        value={values.subject}
                        fullWidth
                        inputProps={{ autoFocus: viewMode === TaskItemViewMode.Create }}
                      />
                      {errors.subject && touched.subject && errors.subject}
                    </Grid>
                    <Grid item>
                      <TextField
                        id={`task-${task.uuid}-description`}
                        label={t('description')}
                        name="description"
                        onChange={handleChange}
                        onBlur={handleBlur}
                        value={values.description}
                        rows="3"
                        multiline
                        fullWidth
                      />
                    </Grid>
                    <Grid item>
                      <Typography variant="subtitle1" gutterBottom>
                        {t('day').toUpperCase()}
                      </Typography>
                      <FieldArray
                        name="daysOfWeek"
                        render={({ replace }) => (
                          <Grid container spacing={2}>
                            {values.daysOfWeek.map((day, idx) => (
                              <Grid item key={`task-weekday-${day.dayOfWeek}`}>
                                <Checkbox
                                  id={`task-weekday-checkbox-${day.dayOfWeek}`}
                                  name={`daysOfWeek.${idx}.dayOfWeek`}
                                  value={day.dayOfWeek}
                                  checked={day.checked}
                                  label={t(`weekdays.${day.dayOfWeek.toLowerCase()}`, {
                                    context: 'short',
                                  })}
                                  onChange={e => {
                                    replace(idx, { ...day, checked: !day.checked })
                                  }}
                                />
                              </Grid>
                            ))}
                          </Grid>
                        )}
                      />
                    </Grid>

                    <FieldArray
                      name="taskSchedules"
                      render={({ remove, replace, push }) => (
                        <>
                          <Grid item>
                            <Typography variant="subtitle1" gutterBottom>
                              {t('schedule_plural').toUpperCase()}
                            </Typography>

                            {values.taskSchedules.map((schedule, idx) => (
                              <Grid
                                container
                                spacing={6}
                                key={`task-weekday-schedule-${schedule.uuid}`}
                              >
                                <Grid item>
                                  <TextField
                                    type="time"
                                    key={`task-schedule-${schedule.uuid}`}
                                    id={`task-schedule-${schedule.uuid}`}
                                    label={t('time')}
                                    name={`taskSchedules.${idx}.time`}
                                    value={schedule.time}
                                    onChange={e => {
                                      const time = e.target.value
                                      replace(idx, { ...schedule, time })
                                    }}
                                    inputProps={{
                                      step: 300, // 5 min
                                    }}
                                  />
                                </Grid>
                                <Grid item>
                                  <RemoveScheduleButton
                                    id={`task-weekday-button-remove-schedule-${schedule.uuid}`}
                                    vanilla
                                    icon
                                    onClick={(): void => {
                                      remove(idx)
                                      // bug in formik makes this necessary
                                      // see https://github.com/jaredpalmer/formik/issues/784#issuecomment-503135849
                                      setTimeout(() => {
                                        validateForm()
                                      }, 10)
                                    }}
                                  >
                                    <CloseIcon />
                                  </RemoveScheduleButton>
                                </Grid>
                              </Grid>
                            ))}
                          </Grid>
                          <Grid item>
                            <Button
                              id="task-weekday-button-add-schedule"
                              ghost
                              onClick={(): void => {
                                push({
                                  time: '00:00:00',
                                  taskUuid: task.uuid,
                                  uuid: uuid(),
                                  dayOfWeek: Weekday.Monday, // temporary value
                                })
                              }}
                            >
                              {t('goals:diet.task.add-schedule')}
                            </Button>
                          </Grid>
                        </>
                      )}
                    />

                    <Grid item>
                      <Grid container justify="flex-end" spacing={2}>
                        <Grid item>
                          <Button
                            id="task-button-cancel-editing"
                            size="small"
                            ghost
                            licorice
                            onClick={
                              viewMode === TaskItemViewMode.Create
                                ? onDeleteTask
                                : (): void => {
                                    handleReset()
                                    onChangeViewMode(TaskItemViewMode.View)
                                  }
                            }
                          >
                            {t('cancel')}
                          </Button>
                        </Grid>
                        <Grid item>
                          <Button
                            type="submit"
                            id="task-button-save-editing"
                            size="small"
                            disabled={!dirty || !isValid || isSubmitting}
                          >
                            {t('ok')}
                          </Button>
                        </Grid>
                      </Grid>
                    </Grid>
                  </Grid>
                </Form>
              )}
            </Formik>
          </EditCardContent>
        </Card>
      )}
    </>
  )
}

1 个答案:

答案 0 :(得分:1)

onEditTask可能正在更改viewMode,从而删除了<EditCardContent>,因此在对setSubmitting的调用发生时,Formik已经被卸载。