React-hook-form Cannot read properties of undefined (reading 'target') - javascript

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

Related

How to use useForm to validate input while using localStorage

I'm trying to validate a simple form with a single input just for practice. I also don't want the value that the user types in the input to disappear after page refresh, for that reason, I did a little bit of searching and found out about saving that data using localStorage. After trying to implement that for a while, I managed to do that, when I refresh the page, the value is still there. However, now, when I'm trying to validate the form using useForm from react-hook-form, It just doesn't work for some reason, when I try to use that same useForm logic with an input without using localStorage, It works just fine, but while trying to add localStorage functionality, then it doesn't. I hope I'm describing my problem at least okey, here's the code :
import React, {useEffect, useState } from "react";
import "./App.css"
import { useForm } from "react-hook-form";
const getForm = () => {
const storedValues = localStorage.getItem("form");
if(!storedValues) return {
name: "",
age: ""
}
return JSON.parse(storedValues);
}
function Home() {
const [values, setValues] = useState(getForm)
const {register, handleSubmit, watch} = useForm();
const handleChange = (e) => {
setValues((previousValues) => ({
...previousValues,
[e.target.name]: e.target.value,
}))
}
const onSubmit = async data => { console.log(data); };
useEffect(()=>{
localStorage.setItem("form", JSON.stringify(values))
}, [values])
return (
<div className="container">
<form onSubmit={handleSubmit(onSubmit)}>
<input value={values.name} onChange={handleChange} name="name" placeholder="name" />
<input value={values.age} onChange={handleChange} name="age" placeholder="age"/>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default Home;
This code works fine since I'm not adding useForm register to the input, but if I do that, then It gets buggy, like this :
<input value={values.name} onChange={handleChange} name="name" placeholder="name" {...register("name")} />
The latest code only works If I remove the value atrribute from the input, but I can't do that, If I do, I can't use localStorage anymore.
Looking at the documentation, you had the syntax a little off with your register function. That function takes a second argument, which is an object of props, and that is where you want to define value, name and onChange.
Like this:
<input
placeholder="name"
{...register("name", {
onChange: handleChange,
name: "name",
value: values.name
})}
/>
Here is the full code I have working on a codesandbox. That's really all I changed, expect removing the watch import.
import React, { useEffect, useState } from "react";
import "./styles.css";
import { useForm } from "react-hook-form";
const getForm = () => {
const storedValues = localStorage.getItem("form");
if (!storedValues)
return {
name: "",
age: ""
};
return JSON.parse(storedValues);
};
function Home() {
const [values, setValues] = useState(getForm);
const { register, handleSubmit } = useForm();
const handleChange = (e) => {
setValues((previousValues) => ({
...previousValues,
[e.target.name]: e.target.value
}));
};
const onSubmit = async (data) => {
console.log(data);
};
useEffect(() => {
localStorage.setItem("form", JSON.stringify(values));
}, [values]);
return (
<div className="container">
<form onSubmit={handleSubmit(onSubmit)}>
<input
placeholder="name"
{...register("name", {
onChange: handleChange,
name: "name",
value: values.name
})}
/>
<input
value={values.age}
onChange={handleChange}
name="age"
placeholder="age"
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default Home;

Unable to type into input field

I'm trying to store user input in local storage and had it functioning. But because I will need to test it, I have changed my code and made a function that will work as a custom hook, that I will call in my tests also.
Now the page is rendering but I am not able to type into the input box?
When hovered over the box the mouse cursor doesn't even respond as if it isn't an input field.
I believe the problem lies in my useStateWithLocalStorage function:
import { useState, useEffect } from 'react';
const useStateWithLocalStorage = (defaultValue, key) => {
const [value, setValue] = useState(() => {
const storedValues = localStorage.getItem(key);
return storedValues !== '' ? JSON.parse(storedValues) : defaultValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
export default useStateWithLocalStorage;
In particular this line
return storedValues !== '' ? JSON.parse(storedValues) : defaultValue;
The value before anything is parsed into the localstorage should be name: ''
Here is my component:
import React from 'react';
import { Container, Title } from '#mantine/core';
import useStateWithLocalStorage from './Handlers';
const UserForm = () => {
const [inputValue, setInputValue] = useStateWithLocalStorage('', 'form');
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
}
function handleChange(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
setInputValue((previousValues) => ({
...previousValues,
[event.target.name]: event.target.value,
}));
}
return (
<Container>
<Title order={2}>Welcome {inputValue.name}</Title>
<form onSubmit={handleSubmit}>
<label htmlFor="name">
Name
<input
type="text"
name="name"
id="name"
placeholder="enter your name"
onChange={handleChange}
value={inputValue.name}
/>
</label>
</form>
</Container>
);
};
export default UserForm;
I hope I've explained myself well enough and haven't wasted anyone's time. I'd be thankful for any help.

A component is changing the uncontrolled value state of Select to be controlled

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>("");

Cannot read property 'name' of undefined in react?

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;

Binding API Data from React Parent Component to Child Components

I'm new to React and am tripping over this issue.
Have read couple of tutorials and questions here to find out about how Parent & Child Components should communicate. However, I am unable to get the data to populate the fields + make it editable at the same time. I'll try explain further in code below:
Parent Component:
...imports...
export default class Parent extends Component {
constructor(props) {
this.state = {
data: null
};
}
componentDidMount() {
API.getData()
.then((response) => {
this.setState({ data: response });
// returns an object: { name: 'Name goes here' }
})
}
render() {
return (
<Fragment>
<ChildComponentA data={this.state.data} />
<ChildComponentB data={this.state.data} />
</Fragment>
);
}
}
Input Hook: (source: https://rangle.io/blog/simplifying-controlled-inputs-with-hooks/)
import { useState } from "react";
export const useInput = initialValue => {
const [value, setValue] = useState(initialValue);
return {
value,
setValue,
reset: () => setValue(""),
bind: {
value,
onChange: event => {
setValue(event.target.value);
}
}
};
};
ChildComponent:* (This works to allow me to type input)
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput('');
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
ChildComponent Component:
(Trying to bind API data - Input still editable but data is still not populated even though it is correctly received.. The API data takes awhile to be received, so the initial value is undefined)
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput(props.data && props.data.name || '');
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
ChildComponent Component:
(Trying to use useEffect to bind the data works but input field cannot be typed..)
I believe this is because useEffect() is trigged every time we type.. and props.data.name is rebinding its original value
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput(props.data && props.data.name || '');
useEffect(() => {
if(props.data) {
setValue(props.data.name);
}
});
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
I can think of a few tricks like making sure it binds only once etc.. But I'm not sure if it is the correct approach. Could someone share some insights of what I could be doing wrong? And what should be the correct practice to do this.
To iterate, I'm trying to bind API data (which takes awhile to load) in parent, and passing them down as props to its children. These children have forms and I would like to populate them with these API data when it becomes available and yet remain editable after.
Thanks!
Basic way to create your Parent/Child Component structure is below, I believe. You don't need a class-based component for what you are trying to achieve. Just add an empty array as a second argument to your useEffect hook and it will work as a componentDidMount life-cycle method.
Parent component:
import React, {useState, useEffect} from 'react';
export default const Parent = () => {
const [data, setData] = useState({});
const [input, setInput] = useState({});
const inputHandler = input => setInput(input);
useEffect(() => {
axios.get('url')
.then(response => setData(response))
.catch(error => console.log(error));
}, []);
return <ChildComponent data={data} input={input} inputHandler={inputHandler} />;
};
Child Component:
import React from 'react';
export default const ChildComponent = props => {
return (
<div>
<h1>{props.data.name}</h1>
<input onChange={(e) => props.inputHandler(e.target.value)} value={props.input} />
</div>
);
};

Categories

Resources