I am using react final form with material UI .when I am adding custom text field or material UI component I am getting this error
Cannot read property 'name' of undefined
here is my code
https://codesandbox.io/s/happy-darkness-uzr2t
import React from "react";
export const TextField = props => {
console.log(props);
const {
input,
label,
meta,
required,
placeholder,
disabledInput,
onKeyPress = () => {},
onInputChange = () => {}
} = props;
const id = input.name;
let { value, ...restProps } = props.input;
return (
<TextField error {...input} id={id} label={label} variant="outlined" />
);
};
export default TextField;
Related
Apologies in advanced, I am really struggling with React-hook-forms, any help is much appreciated.
I've managed to get my form fields prepopulated from a database, but my problem arises when trying to use custom inputs.
I have a Checkbox component like so:
import React from "react";
const Checkbox = React.forwardRef(
(
{
label,
name,
value,
onChange,
defaultChecked,
onBlur,
type,
...rest
}: any,
forwardedRef: any
) => {
const [checked, setChecked] = React.useState(defaultChecked);
React.useEffect(() => {
if (onChange) {
onChange(checked);
}
}, []);
React.useEffect(() => {
}, [checked]);
return (
<div onClick={() => setChecked(!checked)} style={{ cursor: "pointer" }}>
<label htmlFor={name}>{label}</label>
<input
type="checkbox"
value={value}
name={name}
onBlur={onBlur}
ref={forwardedRef}
checked={checked}
{...rest}
/>
[{checked ? "X" : " "}]{label}
</div>
);
}
);
export default Checkbox;
Which gets default values and applies them to the component:
import "./styles.css";
import Checkbox from "./Checkbox";
import { useForm } from "react-hook-form";
const defaultValues = {
gender: "male"
};
export default function App() {
const { handleSubmit, register } = useForm({
defaultValues: defaultValues
});
const onSubmit = async (data: any) => {
console.log(data, "data");
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Checkbox
{...register("gender")}
name={"gender"}
value={"boy"}
label={"Boy"}
// when this added, the custom checks work but everything is checked
// defaultChecked={defaultValues}
/>
</form>
);
}
But when I call the onChange function I get the following error:
Cannot read properties of undefined (reading 'target')
The behavior is working as expected when just clicking on the checkboxes, and the default state is loaded for the native inputs, but it doesn't reflect the custom checkbox UI without user interaction.
Here's a codesandbox showing my issue.
What am I doing wrong
I have existing component called TextField. I would like to use this component as base to create NumberField component. Reason is I don't want to type out exact same code when there are only minor tweaks in the NumberField component. There are 3 parts that I would like to change:
For interface, TextField has type = 'text' | 'search', but I would like to update it to type = 'text' for NumberField component.
I would like to add new prop inputMode = 'numeric' for NumberField.
I would like to update handleChange function for onChange prop.
So I have coded up the following.
TextField.tsx
import React, { useState } from "react";
export interface ITextFieldProps {
type?: "text" | "search";
onChange?: (value: string) => void;
required?: boolean;
}
export const TextField: React.FunctionComponent<ITextFieldProps> = React.forwardRef<
HTMLInputElement, ITextFieldProps>((props, ref) => {
const { type = "text", onChange, required = true } = props;
const [value, setValue] = useState("");
function handleChange(e: React.FormEvent<HTMLInputElement>) {
setValue(e.currentTarget.value);
onChange?.(e.currentTarget.value);
}
return (
<input
type={type}
value={value}
onChange={handleChange}
required={required}
/>
);
});
NumberField.tsx
import React, { useState } from "react";
import { ITextFieldProps, TextField } from "./TextField";
export interface INumberFieldProps extends ITextFieldProps {
type?: "text";
inputMode?: "numeric";
}
export const NumberField: React.FunctionComponent<INumberFieldProps> = React.forwardRef<
HTMLInputElement, INumberFieldProps>((props, ref) => {
const { inputMode = "numeric", onChange } = props;
const [value, setValue] = useState("");
function handleChange(e: React.FormEvent<HTMLInputElement>) {
const updatedValue = e.currentTarget.value.replace(/\D/g, "");
setValue(updatedValue);
onChange?.(updatedValue);
}
return (
<TextField inputMode={inputMode} onChange={handleChange} value={value} />
);
});
App.tsx
import "./styles.css";
import { TextField } from "./TextField";
import { NumberField } from "./NumberField";
export default function App() {
function onChange(e: any) {
console.log(e);
}
return (
<div className="App">
<NumberField onChange={(e) => onChange(e)} />
</div>
);
}
With this code when I go to chrome dev tools and check input element, I only see <input type="text" />, but I expect to see <input type="text" inputmode="numeric" />, since I added inputMode prop. Also with regex in the handleChange function, I should be able to only type numbers in the input, but I get "Cannot read properties of undefined (reading 'value')" error. What's the best way to solve this issue? Source: https://codesandbox.io/s/numberfield-component-ts-wohlok?file=/src/NumberField.tsx
I'm trying to create a edit form to edit data from database by id. I tried this:
import React, {FormEvent, useEffect, useState} from "react";
import TextField from "#material-ui/core/TextField";
import { createStyles, makeStyles, Theme } from "#material-ui/core/styles";
import {
TicketFullDTO,
TicketStatusTypesDTO,
} from "../../service/support/types";
import {
getTicket,
getTicketStatusTypes,
updateTicket,
} from "../../service/support";
import { useHistory, useParams } from "react-router-dom";
import InputLabel from "#mui/material/InputLabel";
import Select from "#mui/material/Select";
import MenuItem from "#mui/material/MenuItem";
import { FormControl } from "#mui/material";
import { Moment } from "moment";
import { RouteParams } from "../../service/utils";
export default function TicketProfile(props: any) {
const classes = useStyles();
let history = useHistory();
let requestParams = useParams<RouteParams>();
const [status, setStatus] = useState<string>("");
const [submitDate, setSubmitDate] = useState<Moment | null>(null);
const [ticket, setTicket] = useState<TicketFullDTO>();
const formSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(e);
updateTicket(requestParams.id, data)
.then(({ data }) => {
console.log(data.title);
history.replace("/support");
})
.catch((err) => {
console.log(err);
});
};
const [ticketCategoriesList, setTicketCategoriesList] = useState<
TicketCategoryTypesDTO[]
>([]);
const [ticket, setTicket] = useState<TicketFullDTO>();
const getSingleTicket = async () => {
getTicket(requestParams.id)
.then(({ data }) => {
setTicket(data);
})
.catch((error) => {
console.error(error);
});
};
const [ticketStatusList, setTicketStatusList] = useState<
TicketStatusTypesDTO[]
>([]);
useEffect(() => {
ticketStatusData();
getSingleTicket();
}, []);
const ticketStatusData = async () => {
getTicketStatusTypes()
.then((resp) => {
setTicketStatusList(resp.data);
})
.catch((error) => {
console.error(error);
});
};
return (
<Container>
<form onSubmit={onSubmit}>
.........
<TextField
value={ticket?.title}
id="title"
onChange={({ target: { value } }) => {
setTicket({ ...ticket, title: value });
}}
/>
.........
<FormControl>
<TextField
label="Submit Date"
id="submit-date"
type="date"
defaultValue={ticket?.submitDate}
//#ts-ignore
onInput={(e) => setSubmitDate(e.target.value)}
/>
</FormControl>
..........
<Select
labelId="status-label"
id="status-helper"
value={ticket?.status}
onChange={(e) => setStatus(e.target.value)}
required
>
{ticketStatusList.map((element) => (
<MenuItem value={element.code}>
{element.name}
</MenuItem>
))}
</Select>
</FormControl>
...........
<Button
type="submit"
>
Update Ticket
</Button>
</Container>
);
}
.....
export async function updateTicket(
id: string,
data: TicketFullDTO
): Promise<AxiosResponse<TicketFullDTO>> {
return await axios.post<TicketFullDTO>(
`${baseUrl}/management/support/tickets/ticket/${id}`,
{
data,
}
);
}
export interface TicketFullDTO {
id?: number,
title?: string,
status?: string,
submitDate?: Moment | null
}
I get error in Chrome console:
MUI: A component is changing the uncontrolled value state of Select to be controlled. Elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled Select element for the lifetime of the component. The nature of the state is determined during the first render. It's considered controlled if the value is not undefined.
The value for Select should be selected using the value ticket?.status when list ticketStatusList But the data object is not running before rendering the UI content and the value into Select dropdown is not selected.
Do you know how I can fix this issue?
React figures out if a component is controlled or not by checking if value is set on the first render. defaultValue should only be used for uncontrolled components.
Since you're working with controlled components you must provide a default value other than undefined in the value prop:
<Select
labelId="status-label"
id="status-helper"
value={ticket?.status ?? null}
onChange={(e) => setStatus(e.target.value)}
required
>
More on this topic in the docs.
Try this
<Select
labelId="status-label"
id="status-helper"
value={status}
onChange={(e) => setStatus(e.target.value)}
required
>
And to synchronize status ticket with ticket state
const [status, setStatus] = useState<string>("");
const [submitDate, setSubmitDate] = useState<Moment | null>(null);
const [ticket, setTicket] = useState<TicketFullDTO>();
React.useEffect(() => setTicket(previousTicket =>
({ ...previousTicket, status })), [status]);
First, in your MenuItem, set the value prop to object (element) instead of a string (element.code), this is because you pass an object as a value prop to your Select. The current value of the Select must have the same type as the value in MenuItem:
<Select
labelId="status-label"
id="status-helper"
required
value={status} // <------------------------- because value is an object here
onChange={(e) => setStatus(e.target.value)}
>
{ticketStatusList.map((element) => (
<MenuItem
key={element.code}
value={element} // <--- this one should be an object too as a result
>
{element.name}
</MenuItem>
))}
</Select>
Then in your status state declaration, add a null object to change your Select to controlled mode
const [status, setStatus] = useState<TicketStatusTypesDTO>({});
for typescript error: you need to write the same type and just set value only if its not null, so type error won't bother you
const handleChange = (value: string | null) => {
if (!value) {
setStatus(value)
} else setStatus('')
}
<Select
labelId="status-label"
id="status-helper"
value={ticket.status || null} // default value
onChange={(event, value) => handleChange(value)}
required
>
The problem is that the optional chaining operator returns undefined. Try replacing
value={ticket?.status}
with
value={ticket?.status || null}
To solve the TypeScript error, in the useState hook, just declare all possible types of the state variable:
const [status, setStatus] = useState<string | null>("");
I am using the following code from a tutorial as a re-usable component for a Material-UI select:
import React from 'react';
import { TextField, MenuItem } from '#material-ui/core';
import { useField, useFormikContext } from 'formik';
const SelectWrapper = ({
name,
options,
...otherProps
}) => {
const { setFieldValue } = useFormikContext();
const [field, meta] = useField(name);
const handleChange = evt => {
const { value } = evt.target;
const { innerText } = evt.nativeEvent.target;
setFieldValue(name, value);
};
const configSelect = {
...field,
...otherProps,
select: true,
variant: 'outlined',
fullWidth: true,
onChange: handleChange
};
if (meta && meta.touched && meta.error) {
configSelect.error = true;
configSelect.helperText = meta.error;
}
return (
<TextField {...configSelect}>
{Object.keys(options).map((item, pos) => {
return (
<MenuItem key={pos} value={item}>
{options[item]}
</MenuItem>
)
})}
</TextField>
);
};
export default SelectWrapper;
When using this Select.js component within my App.js, i.e.:
<Grid item xs={12}>
<Select
name="country"
label="Country"
options={countries}
/>
</Grid>
is there anyway I can also access the text value from
const { innerText } = evt.nativeEvent.target;
within the App.js component as I also need to get the Country name/text in my select list?
I just need to retrieve the text value from the selection the user made and store it in state.
This should work:
Define a state in your app.js file to hold the innerText value from the child component (Select.js in this case).
const [innerTextSync, setInnerTextSync] = useState('')
Define a function that changes the state in your app.js:
const updateInnerTextSync=(input)=>{setInnerTextSync(input)}
Pass this function as a prop into Select.js
<Select
name="country"
label="Country"
options={countries}
updateInnerTextSync={updateInnerTextSync}
/>
Within the handleChange function in Select.js file, update the app state by calling the function you passed in earlier.
const handleChange = evt => {
const { value } = evt.target;
const { innerText } = evt.nativeEvent.target;
setFieldValue(name, value);
updateInnerTextSync(innerText)
};
Now the innerText should be available in ur app file.
I have the below common component
import PropTypes from 'prop-types';
import Input from '../component/Input'; //internal component
const CustomInput = props => {
const { label, updateInputField } = props;
return (
<Input
label={label}
changeField={updateInputField}
)
}
CustomInput.propTypes = {
label: PropTypes.string,
updateInputField: PropTypes.func
}
export default CustomInput;
Now i am using these component at several places like below
<CustomInput
label="401Balance"
updateInputField={inputFieldHandler}
/>
so this one works.. but there are some places where I am not using the updateInputField as a prop. for e.g.
<CustomInput
label="savingsBalance"
/>
How do i not pass the prop and ensure it doesnot fail. Can someone please suggest.
You could either set a default value to secure the case if no function was passed:
const { label, updateInputField = () => {} } = props;
or (probably a better approach) - create a separate function and add a simple condition:
const CustomInput = props => {
const { label, updateInputField } = props;
const fn = () => {
if (updateInputField) {
updateInputField();
}
};
return (
<Input
label={label}
changeField={fn}
/>
);
}