I am using DatePicker in a custom component and everything works fine including react-hook-form Controllers component for validation. But the top-part of the DatePicker does not display properly. Below is a preview of how it displays.
Here is is how I am using this component to create my own re-useable component. I am at a loss to explain why this is happening. Please any help would be very well appreciated.
Thanks in advance.
import React, { Fragment } from "react";
import { makeStyles } from '#material-ui/core/styles';
import DateFnsUtils from '#date-io/date-fns';
import { MuiPickersUtilsProvider, DatePicker} from '#material-ui/pickers';
import 'date-fns';
import { Control, Controller } from "react-hook-form";
import { Keys } from "../Profile/interfaces";
import { alpha } from '#material-ui/core/styles'
const useStyles = makeStyles((theme) => ({
formControl: {
marginTop: "10px",
marginBottom: "10px",
minWidth: 220,
},
}));
interface Props {
id: string,
label: string,
control: Control<any,any>
required?: boolean,
name: Keys,
requiredMsg?: string,
disabled?: boolean
}
const CustomDate: React.FC<Props> = ({id,label,control, required=false, name, requiredMsg,
disabled = false}) => {
const classes = useStyles();
return(
<div className={classes.formControl}>
<Controller
name={name}
control={control}
rules={{required: required ? requiredMsg : null}}
render={({ field }) =>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<DatePicker
disabled={disabled}
format="dd/MM/yyyy"
inputVariant="filled"
id={id}
autoOk
label={label}
clearable
disableFuture
{...field}
/>
</MuiPickersUtilsProvider>
}
/>
</div>
);
}
export default CustomDate;
Related
I have a CustomTextBox component that is wrapped in react-hook-form Controller component and all works fine including validation and displaying the error message with ErrorMessage component.
The only thing left to-do is to setFocus on the fields when there is errors in the form. This is my first TypeScript project and i'm struggling to find solutions which are similar to mine.
I tried useRef but this only give compile time error => "Property 'ref' does not exist on type 'IntrinsicAttributes".
Below is my Custom component. Please guys all help will be appreciated. Thanks in advance
import React, { useRef } from "react";
import TextField from '#material-ui/core/TextField';
import { Control, Controller } from "react-hook-form";
import { Keys } from '../Profile/interfaces';
interface Props {
id: string;
label: string,
variant: "filled" | "standard" | "outlined",
disabled?: boolean,
control: Control<any,any>
required?: boolean,
name: Keys,
requiredMsg?: string
}
const CustomTextBox: React.FC<Props> = ({id, label, variant,disabled=false, control,
required=false, name, requiredMsg}) => {
const inputRef = useRef<React.RefObject<HTMLButtonElement>>();
return (
<Controller
ref={inputRef}
name={name}
control={control}
rules={{required: required ? requiredMsg : null}}
render={({ field }) =>
<TextField inputRef={field.ref} InputLabelProps={{ shrink: true }} id={id} label={label} variant={variant}
disabled={disabled} {...field}
style={{marginTop: '10px', marginBottom: '10px', minWidth: '250px'}} /> }
/>
);
}
export default CustomTextBox;
So thanks to #Victor Luft i was able to get it right with the below code. Also this is not in the component itself but rather on the page/component that uses Custom Components. This will focus on any element that has an error in your react-hook-form form tag. I hope that makes sense.
Thanks again Victor
useEffect(() => {
const firstErrorKey = Object.keys(errors).find((key) => errors[key]);
if (firstErrorKey) {
(document.querySelector(
`input[name="${firstErrorKey}"]`
) as HTMLInputElement | null)?.focus();
}
}, [Object.keys(errors)]);
You are using field.ref as inputRef for the TextField component. It is very likely that this will be assigned to the native <input /> element. And that is the one, you want to be able to call focus() on.
import React, { useRef } from "react";
import TextField from '#material-ui/core/TextField';
import { Control, Controller } from "react-hook-form";
import { Keys } from '../Profile/interfaces';
interface Props {
id: string;
label: string,
variant: "filled" | "standard" | "outlined",
disabled?: boolean,
control: Control<any,any>
required?: boolean,
name: Keys,
requiredMsg?: string
}
const CustomTextBox: React.FC<Props> = ({id, label, variant,disabled=false, control,
required=false, name, requiredMsg}) => {
const inputRef = useRef<React.RefObject<HTMLButtonElement>>();
return (
<Controller
ref={inputRef}
name={name}
control={control}
rules={{required: required ? requiredMsg : null}}
render={({ field, fieldState }) => {
// don't know who to compute this state as I don't know this lib
const hasError = fieldState === 'error';
if (field.ref.current && fieldState === 'error') field.ref.current.focus();
return (
<TextField inputRef={field.ref} InputLabelProps={{ shrink: true }} id={id} label={label} variant={variant}
disabled={disabled} {...field}
style={{marginTop: '10px', marginBottom: '10px', minWidth: '250px'}} />);
}}
/>
);
}
export default CustomTextBox;
Description
I'm working with a formik form using mui for styling, I wanted to replace an input component with something more advanced as shown below:
My intention is to update formik context and make this selection behave as a regular input form. Unfortunatelly I have not found a lot of documentation or examples of this, and I would like to know best practices for a case like this.
What I tried
This is what I tried, I used mui FormControl enclosing some custom HTML. This works but I can not clear the error when the user moves to the next page. See code below:
Question
I would like to know how to manually clear the error in Formik or if there is a better way to achieve this using mui and formik. Any help is appreciated. Thanks.
import React, { useState } from 'react';
import { useFormikContext } from 'formik';
import { at } from 'lodash';
import { useField } from 'formik';
import { FormLabel, FormControl, FormHelperText, Paper, makeStyles } from '#material-ui/core';
import Grid from '#material-ui/core/Grid';
import { clsx } from 'clsx';
const useStyles = makeStyles({
paper: {
padding: 20,
cursor: 'pointer'
},
active: {
color: '#4285F4'
},
label: {
paddingBottom: 20
}
});
export default function Toggle(props) {
const classes = useStyles();
const { name, label, options, ...rest } = props;
const [field, meta] = useField(props);
const [touched, error] = at(meta, 'touched', 'error');
const isError = touched && error && true;
const { values: formValues } = useFormikContext();
const [activeValue, setActiveValue] = useState(formValues[name]);
function _renderHelperText() {
if (isError) {
return <FormHelperText>{error}</FormHelperText>;
}
}
function _handleSelection(event) {
formValues[name] = event.target.dataset.value;
setActiveValue(event.target.dataset.value);
}
return (
<FormControl {...rest} error={isError}>
<FormLabel className={classes.label}>{label}</FormLabel>
<Grid sx={{ flexGrow: 1 }} container spacing={2}>
{options.map((option, index) => (
<Grid key={index} item>
<Paper
name={name}
data-name={name}
data-value={option.value}
onClick={_handleSelection}
className={clsx(classes.paper, { [classes.active]: option.value == activeValue })}>
{option.lowerValue} - {option.higherValue}
</Paper>
</Grid>
))}
</Grid>
{_renderHelperText()}
</FormControl>
);
}
My yup validation section
CreditScore: {
name: 'Credit Score',
render: <CreditScoreStep />,
formField: {
credit_score: {
name: 'credit_score',
label: 'Credit Score',
initial: ''
}
},
validationSchema: Yup.object().shape({
credit_score: Yup.number().required('Credit Score is required')
})
},
I'm using React Hook Form & the MUI DatePicker Component with Typescript. I have a DateField component which is a reusable component containing the DatePicker.
<DateField<ReceptionInvoice>
label="Issue Date"
control={control}
name={"invoiceDate"}
options={{
required: "This field is required",
}}
error={errors.invoiceDate}
renderInput={(props) => (
<TextField
register={register}
label={props.label}
name={"invoiceDate"}
/>
)}
/>
The TextField component the MUI textField component.
The DateField component has the following code:
import DateFnsUtils from "#date-io/date-fns";
import DatePicker, { DatePickerProps } from "#mui/lab/DatePicker";
import React, { useMemo } from "react";
import { Control, Controller, FieldValues } from "react-hook-form";
import { ReceptionInvoice } from "../models/InvoiceReception";
import { ENOFieldOverrides, ENOFieldProps } from "./EnoFieldUtils";
import ENOTooltip from "./ENOTooltip";
import { makeStyles } from "#mui/styles";
import AdapterDateFns from "#mui/lab/AdapterDateFns";
import LocalizationProvider from "#mui/lab/LocalizationProvider";
export interface ENODateFieldProps<T extends FieldValues>
extends Omit<ENOFieldProps<T>, "register">,
Omit<DatePickerProps, ENOFieldOverrides | "value" | "onChange"> {
control: Control<T, object>;
}
const useStyles = makeStyles({
fullWidth: {
width: "100%",
},
});
export default function ENODateField<T extends FieldValues>({
name,
control,
options,
error,
tooltipText,
...rest
}: ENODateFieldProps<T>) {
const classes = useStyles();
return (
<ENOTooltip text={tooltipText}>
{/* MUI Tooltip wrapper, not relavant */}
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Controller
name={name}
control={control}
render={({ field: { ref, ...fieldRest } }) => (
<DatePicker className={classes.fullWidth} {...fieldRest} {...rest} />
)}
/>
</LocalizationProvider>
</ENOTooltip>
);
}
Using this code, I get the following error:
Please help me out, looking forward to your reply!
I'm using react-hook-form V7.14.2 and I'm using a Material-UI checkbox that can be checked by the user manually or will be automatically checked depending on the values of certain inputs.
This is working OK.
The issue is that when submitting the form, if the check box was checked via logic, depending on the values of certain input, the value sent to the back end will be false.
If the user manually checks the check box, then it will send the correct value. -True if checked-.
I read the documentation to migrate from V6 to V7 in react-hook-form page, as I was previously using V6 and the code works there.
https://react-hook-form.com/migrate-v6-to-v7/
I also read some previous answers in Stackoverflow such as this one: material-ui checkbox with react-hook-form but with no success.
Here is my code:
import React, { useContext, useState, useEffect } from "react";
import Checkbox from "#material-ui/core/Checkbox";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCheckCircle } from "#fortawesome/free-solid-svg-icons";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import { Controller, set } from "react-hook-form";
import { makeStyles } from "#material-ui/core/styles";
import { DarkThemeContext } from "../../context/DarkThemeContext";
import { useStylesSelectPropDetails } from "../../styledComponent/SelectPropDetailsStyled";
import { IMAGES_LINK } from "../../api/doFetch";
const useStylesLabel = makeStyles((theme) => ({
label: {
fontSize: "1em",
fontFamily: "Open Sans Hebrew, sans-serif !important",
fontWeight: 400,
lineHeight: 1.5,
},
}));
const CheckboxPropDetails = ({
register,
name,
label,
checked,
isShelter,
isElevator,
}) => {
const classesLabel = useStylesLabel();
const [isDarkTheme] = useContext(DarkThemeContext);
const [isChecked, setIsChecked] = useState(false);
const props = { isDarkTheme: isDarkTheme };
const classes = useStylesSelectPropDetails(props);
const handleChange = (event) => {
setIsChecked(event.target.checked);
};
useEffect(() => {
if (name === "shelter") {
if ( isShelter,) {
setIsChecked(true);
} else setIsChecked(false);
}
if (name === "elevator") {
console.log("elevator");
if (isElevator) {
console.log("isElevator", isElevator);
setIsChecked(true);
} else setIsChecked(false);
}
}, [ isShelter, isElevator]);
return (
<>
<FormControlLabel
classes={classesLabel}
control={
<Checkbox
name={name}
id={name}
defaultChecked={checked ? checked : false}
{...register(name)}
color="primary"
size={"medium"}
icon={
<img
src={`${IMAGES_LINK}/dashboard/elements/rec.svg`}
alt="Circle"
style={{ width: 20, height: 20 }}
/>
}
disableRipple
checkedIcon={
<FontAwesomeIcon
icon={faCheckCircle}
style={{ width: 20, height: 20 }}
/>
}
checked={isChecked}
onChange={(e) => handleChange(e)}
/>
}
labelPlacement="end"
label={<p className={classes.paragraphAddProp}>{label}</p>}
/>
</>
);
};
Code used with V6 on react-hook-form:
import React, { useContext, useState, useEffect } from "react";
import Checkbox from "#material-ui/core/Checkbox";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCheckCircle } from "#fortawesome/free-solid-svg-icons";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import { Controller } from "react-hook-form";
import { makeStyles } from "#material-ui/core/styles";
import { DarkThemeContext } from "../../context/DarkThemeContext";
import { useStylesSelectPropDetails } from "../../styledComponent/SelectPropDetailsStyled";
import { IMAGES_LINK } from "../../api/doFetch";
const useStylesLabel = makeStyles((theme) => ({
label: {
fontSize: "1em",
fontFamily: "Open Sans Hebrew, sans-serif !important",
fontWeight: 400,
lineHeight: 1.5,
},
}));
const CheckboxPropDetails = ({
register,
name,
label,
checked,
isShelter,
isElevator,
}) => {
const classesLabel = useStylesLabel();
const [isDarkTheme] = useContext(DarkThemeContext);
const props = { isDarkTheme: isDarkTheme };
const classes = useStylesSelectPropDetails(props);
const [isChecked, setIsChecked] = useState(false);
const handleChange = (event) => {
console.log("event", event.target.checked);
setIsChecked(event.target.checked);
};
useEffect(() => {
if (name === "shelter") {
if (isShelter) {
setIsChecked(true);
} else setIsChecked(false);
}
if (name === "elevator") {
if (isElevator) {
setIsChecked(true);
} else setIsChecked(false);
}
}, [isShelter, isElevator]);
return (
<>
<FormControlLabel
classes={classesLabel}
control={
<Checkbox
name={name}
defaultChecked={checked ? checked : false}
inputRef={register}
color="primary"
size={"medium"}
icon={
<img
src={`${IMAGES_LINK}/dashboard/elements/rec.svg`}
alt="Circle"
style={{ width: 20, height: 20 }}
/>
}
disableRipple
checkedIcon={
<FontAwesomeIcon
icon={faCheckCircle}
style={{ width: 20, height: 20 }}
/>
}
checked={isChecked}
onChange={(e) => handleChange(e)}
/>
}
labelPlacement="end"
label={<p className={classes.paragraphAddProp}>{label}</p>}
/>
</>
);
};
Control, register, and so are being pass as props from another component.
As mention before, it's working on V6.
The difference in V6 and V7 is one line:
in V6
inputRef={register}
in V7
{...register(name)}
How could I make it so that when the checked value is change via logic that it will register and on submit will be send to the backend as true?
The MUI <Checkbox /> component has a slighty different interface for setting a value. The RHF value has to be set with checked prop.
In v6 you just passed register to the inputRef, but since v7 calling register will return an object - two properties of this object are value and onChange. So when spreading your register call to the MUI <Checkbox /> you're setting the link for the actual value to the value prop instead of using the checked prop.
You should therefore use RHF's <Controller /> component, check the docs here for more infos about integrating external controlled components.
<FormControlLabel
control={
<Controller
name="checkbox"
control={control}
defaultValue={false}
render={({ field: { value, ref, ...field } }) => (
<Checkbox
{...field}
inputRef={ref}
checked={!!value}
color="primary"
size={"medium"}
disableRipple
/>
)}
/>
}
label="Checkbox"
labelPlacement="end"
/>
One important note: you have to set the value with checked={!!value} to avoid a warning about changing a component from uncontrolled to controlled in case that your defaultValue for someCheckbox is initially undefined.
I am trying to fit a datetime picker inside the paper of MUI autocomplete.
Tried mui datepicker and tried to force open the datepicker but no luck.
The native seems to work to an extend but it still needs to wire the date selection click to close the poper.
here is demo:
https://codesandbox.io/s/material-demo-forked-pmgzp
Plz advise
I was trying to integrate DatePicker with autocomplete. I did it with useAutoComplete
import * as React from "react";
import { useAutocomplete } from "#mui/base/AutocompleteUnstyled";
import { DateRangePicker } from "mui-daterange-picker";
import { TextField } from "#mui/material";
import InputAdornment from "#mui/material/InputAdornment";
import CalendarMonthOutlinedIcon from "#mui/icons-material/CalendarMonthOutlined";
import moment, { Moment } from "moment";
export interface IDatePickerProps {
name: string;
startDateLabel: string;
endDateLabel: string;
value: [Moment, Moment];
setFieldValue: (
field: string,
value: [Moment, Moment],
shouldValidate?: boolean
) => void;
}
export default function UseAutocomplete({
name = "datepicker",
value = [moment(), moment().add(1, "months")],
startDateLabel = "2022-10-1",
endDateLabel = "2022-12-1",
setFieldValue
}) {
const { getRootProps, getInputLabelProps, getInputProps } = useAutocomplete({
id: "use-autocomplete-demo",
options: []
});
const [open, setOpen] = React.useState(false);
const toggle = () => setOpen(!open);
return (
<div>
<>
<div {...getRootProps()}>
<label {...getInputLabelProps()}>{name}</label>
<TextField
{...getInputProps()}
name={name}
placeholder={`${startDateLabel} - ${endDateLabel}`}
onClick={toggle}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<CalendarMonthOutlinedIcon />
</InputAdornment>
)
}}
value={`${moment(value[0]).format("YYYY-MM-DD")} - ${moment(
value[1]
).format("YYYY-MM-DD")}`}
/>
</div>
{
<DateRangePicker
open={open}
toggle={toggle}
onChange={(range) => {
let selectedValues = [
moment(range.startDate),
moment(range.endDate)
];
setFieldValue(selectedValues);
}}
/>
}
</>
</div>
);
}
Output: