776
Recently we have built a large application in React, using Formik for the Forms and MUI for the interface. For that, we created various components (Checkbox, Text input, Date picker, Time picker, Select, Code editor component).
Below you’ll find the complete components! Hope it helps someone.
All components include showing a title above the field, and an error display component.
Formik Checkbox input component
import React from 'react'
import { FormikContextType, useFormikContext } from 'formik'
import { Checkbox, FormControlLabel } from '@mui/material'
import * as _ from 'lodash'
interface AppCheckboxInputProps {
name: string
label?: string
required?: boolean
[x: string]: any
}
const AppCheckboxInput: React.FC<AppCheckboxInputProps> = ({ name, label, required = false, ...otherProps }) => {
const { setFieldTouched, setFieldValue, values }: FormikContextType<any> = useFormikContext()
return (
<FormControlLabel
control={
<Checkbox
id={name}
required={required}
checked={_.get(values, name) || false}
onBlur={() => setFieldTouched(name)}
onChange={event => setFieldValue(name, event.target.checked)}
{...otherProps}
/>
}
label={label}
/>
)
}
export default AppCheckboxInput
Formik Datepicker input
This component uses Luxon for date handling.
import React, { ReactNode } from 'react'
import { FormikContextType, useFormikContext } from 'formik'
import DatePickerWrapper from 'src/@core/styles/libs/react-datepicker'
import nl from 'date-fns/locale/nl'
import DatePicker from 'react-datepicker'
import { DateTime } from 'luxon'
// ** Custom Component Imports
import CustomTextField from 'src/@core/components/mui/text-field'
interface AppDateInputProps {
name: string
label: string
required?: boolean
[x: string]: any
}
const AppDateInput: React.FC<AppDateInputProps> = ({ name, label, required = false, ...otherProps }) => {
const { setFieldTouched, setFieldValue, values, errors, touched }: FormikContextType<any> = useFormikContext()
const onChange = (event: any) => {
setFieldValue(name, event)
}
const setInputValue = (value: any) => {
if (value && value instanceof Date) {
return value
} else if (value) {
return DateTime.fromISO(value).toJSDate()
}
}
return (
<div>
<DatePicker
selected={setInputValue(values[name]) as any}
popperPlacement='bottom'
locale={nl}
dateFormat={'dd-MM-yyyy'}
onChange={event => onChange(event)}
placeholderText='Click to select a date'
{...otherProps}
customInput={
<CustomTextField
fullWidth={true}
label={label || ''}
onBlur={() => setFieldTouched(name)}
error={(touched[name] && errors[name]) as boolean}
helperText={touched[name] && (errors[name] as ReactNode)}
required={required}
/>
}
/>
</div>
)
}
export default AppDateInput
Formik Dropzone input
// ** Third Party Components
import { useDropzone } from 'react-dropzone'
// ** Styles
import { FormikContextType, useFormikContext } from 'formik'
import { useState } from 'react'
import { Box, Typography, Stack } from '@mui/material'
import Icon from 'src/@core/components/icon'
interface AppDropzoneInputProps {
name: string
callback?: (e: string) => void
}
const AppDropzoneInput: React.FC<AppDropzoneInputProps> = ({ name, callback }) => {
const { getRootProps, getInputProps } = useDropzone({
onDrop: files => {
setFieldValue(name, files[0])
setFiles(files)
setFieldTouched(name)
if (callback) callback(files[0].name)
}
})
const [files, setFiles] = useState<File[]>([])
const { setFieldValue, setFieldTouched }: FormikContextType<any> = useFormikContext()
return (
<Box {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
<Box
sx={{
border: '2px dashed rgba(47, 43, 61, 0.16)',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
flexDirection: 'column'
}}
>
{!files.length && (
<>
<Box
sx={{
mb: 8.75,
width: 48,
height: 48,
display: 'flex',
borderRadius: 1,
alignItems: 'center',
justifyContent: 'center'
}}
>
<Icon icon='tabler:upload' fontSize='1.75rem' />
</Box>
<Typography variant='h4' sx={{ mb: 2.5 }}>
Zet de bestanden hier neer of klik om te bladeren
</Typography>
</>
)}
{files.length > 0 && (
<Box sx={{ marginTop: 10, marginBottom: 10 }}>
<Typography variant='h6' sx={{ mb: 2.5 }}>
Geselecteerd bestand (klik om een andere te selecteren):
</Typography>
<Stack direction='row' spacing={2}>
<Icon icon='tabler:file-check' fontSize='1.25rem' />
<Typography variant='h6' sx={{ color: 'text.secondary' }}>
{files[0]?.name}
</Typography>
</Stack>
</Box>
)}
</Box>
</Box>
)
}
export default AppDropzoneInput
Formik Text input
import { FormikContextType, useFormikContext } from 'formik'
import { ReactNode } from 'react'
import CustomTextField from 'src/@core/components/mui/text-field'
import * as _ from 'lodash'
interface AppTextInputProps {
name: string
label?: string
required?: boolean
disabled?: boolean
multiline?: boolean
maxRows?: number
callback?: (value: string) => void
[x: string]: any
}
const AppTextInput: React.FC<AppTextInputProps> = ({
name,
label,
required = false,
disabled = false,
multiline,
maxRows,
callback,
...otherProps
}) => {
const { setFieldTouched, setFieldValue, values, errors, touched }: FormikContextType<any> = useFormikContext()
const onChange = (event: any) => {
const inputType = event.target.type
if (inputType === 'number') {
const targetValue = event.target.value.replace(',', '.')
setFieldValue(name, targetValue)
} else {
setFieldValue(name, event.target.value)
}
if (callback) {
callback(event.target.value)
}
}
return (
<div>
<CustomTextField
fullWidth
disabled={disabled}
label={label}
maxRows={maxRows}
multiline={multiline}
error={(_.get(touched, name) && _.get(errors, name)) as boolean}
required={required}
value={_.get(values, name)}
onBlur={() => setFieldTouched(name, true)}
onChange={event => onChange(event)}
helperText={_.get(touched, name) && (_.get(errors, name) as ReactNode)}
{...otherProps}
/>
</div>
)
}
export default AppTextInput
Formik Selectable tree component
This component uses the react-checkbox-tree component (Github), and Fontawesome for the icons.
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { FormikContextType, useFormikContext } from 'formik'
import React, { useState } from 'react'
import { Typography } from '@mui/material'
import CheckboxTree, { Node } from 'react-checkbox-tree'
import * as _ from 'lodash'
interface AppTreeFormInputProps {
name: string
treeData: Node[]
checkStrictly?: boolean
disabled?: boolean
title: string
}
const AppTreeFormInput: React.FC<AppTreeFormInputProps> = ({
name,
treeData,
checkStrictly,
disabled,
title,
...props
}) => {
const { setFieldValue, values }: FormikContextType<any> = useFormikContext()
const [expanded, setExpanded] = useState<string[]>([])
const onCheck = (checkedKeys: any) => {
if (checkStrictly) {
setFieldValue(name, checkedKeys.checked as string[])
} else {
setFieldValue(name, checkedKeys as string[])
}
}
return (
<>
<Typography variant='h4' sx={{ mb: 1 }}>
{title}
</Typography>
<CheckboxTree
nodes={treeData}
checked={_.get(values, name)}
onCheck={checked => onCheck(checked)}
onExpand={expanded => setExpanded(expanded)}
expanded={expanded}
icons={{
check: <FontAwesomeIcon className='rct-icon rct-icon-check' icon={['far', 'square-check']} />,
uncheck: <FontAwesomeIcon className='rct-icon rct-icon-uncheck' icon={['far', 'square']} />,
halfCheck: <FontAwesomeIcon className='rct-icon rct-icon-half-check' icon='check-square' />,
expandOpen: <FontAwesomeIcon className='rct-icon rct-icon-expand-open' icon='chevron-down' fontSize={15} />,
expandAll: <FontAwesomeIcon className='rct-icon rct-icon-expand-all' icon='plus-square' fontSize={15} />,
collapseAll: <FontAwesomeIcon className='rct-icon rct-icon-collapse-all' icon='minus-square' />,
parentClose: <FontAwesomeIcon className='rct-icon rct-icon-parent-close' icon='folder' />,
parentOpen: <FontAwesomeIcon className='rct-icon rct-icon-parent-open' icon='folder-open' />,
leaf: <FontAwesomeIcon className='rct-icon rct-icon-leaf-close' icon='file' />,
expandClose: <FontAwesomeIcon className='rct-icon rct-icon-expand-close' icon='chevron-right' />
}}
/>
</>
)
}
export default AppTreeFormInput
How to use the components?
All components can be used inside the Formik content, so for example:
<Formik
enableReinitialize={true}
initialValues={
{
name: data?.data ? data?.data?.name : '',
} as AddOrUpdateUserInput
}
onSubmit={handleSubmit}
validationSchema={validationSchema}
>
{({ handleSubmit }: FormikProps<AddOrUpdateUserInput>) => (
<Form onSubmit={handleSubmit}>
<Grid container spacing={4}>
<Grid item xs={6}>
<AppTextInput name='name' label='Firstname' required />
</Grid>
</Grid>
</Form>
)}
</Formik>