I get this error when trying to submit a Formik form.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. in Formik (at layout.tsx:113) in div (created by ForwardRef(CardContent)) in ForwardRef(CardContent) (created by WithStyles(ForwardRef(CardContent))) in WithStyles(ForwardRef(CardContent)) (created by Context.Consumer) in StyledComponent (created by Styled(WithStyles(ForwardRef(CardContent)))) in Styled(WithStyles(ForwardRef(CardContent))) (created by CardContent) in CardContent (created by Context.Consumer) in StyledComponent (created by Styled(CardContent)) in Styled(CardContent) (at layout.tsx:112) in div (created by ForwardRef(Paper)) in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper))) in WithStyles(ForwardRef(Paper)) (created by ForwardRef(Card)) in ForwardRef(Card) (created by WithStyles(ForwardRef(Card))) in WithStyles(ForwardRef(Card)) (created by Context.Consumer) in StyledComponent (created by Styled(WithStyles(ForwardRef(Card)))) in Styled(WithStyles(ForwardRef(Card))) (created by Card) in Card (at layout.tsx:111)
The function onSubmit has no async calls:
<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)
})
}}
>
The onEditTask callback just updates the list of TaskItems.
The full TaskItem component looks like this. The '@web/core...' components are my own wrappers around Material UI components.
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>
)}
</>
)
}