InvalidValueError: not an instance of HTMLInputElement in React project - javascript

I'm trying to add in my React project a google autocomplete feature to my location input but I get this error: InvalidValueError: not an instance of HTMLInputElement.
I guess when the event fires I get the error but I cannot figure out where it comes from.
Here's my code
Search.js
import React, { useState, useContext } from "react";
import DisplaySearchBar from "../layout/DisplaySearchBar";
import RestContext from "../context/restaurant/restContext";
import AlertContext from "../context/alert/alertContext";
const Search = () => {
const restContext = useContext(RestContext);
const alertContext = useContext(AlertContext);
const [where, setWhere] = useState("");
const [what, setWhat] = useState("");
const [sortBy, setSortBy] = useState("best_match");
const [city, setCity] = useState("");
const sortByOptions = {
"Best Match": "best_match",
"Highest Rated": "rating",
"Most Reviewed": "review_count",
};
// give active class to option selected
const getSortByClass = (sortByOption) => {
if (sortBy === sortByOption) {
return "active";
} else {
return "";
}
};
// set the state of a sorting option
const handleSortByChange = (sortByOption) => {
setSortBy(sortByOption);
};
//handle input changes
const handleChange = (e) => {
if (e.target.name === "what") {
setWhat(e.target.value);
} else if (e.target.name === "where") {
setWhere(e.target.value);
}
};
const onSubmit = (e) => {
e.preventDefault();
if (where && what) {
restContext.getRestaurants({ where, what, sortBy });
setWhere("");
setWhat("");
setSortBy("best_match");
} else {
alertContext.setAlert("Please insert somethiing", "error");
}
};
// displays sort options
const renderSortByOptions = () => {
return Object.keys(sortByOptions).map((sortByOption) => {
let sortByOptionValue = sortByOptions[sortByOption];
return (
<li
className={getSortByClass(sortByOptionValue)}
key={sortByOptionValue}
onClick={() => handleSortByChange(sortByOptionValue)}
>
{sortByOption}
</li>
);
});
};
// google suggestion
const handleScriptLoad = () => {
const handlePlaceSelect = () => {
// Extract City From Address Object
const addressObject = autocomplete.getPlace();
const address = addressObject.address_components;
// Check if address is valid
if (address) {
// Set State
setCity(address[0].long_name);
}
};
const options = {
types: ["(cities)"],
}; // To disable any eslint 'google not defined' errors
// Initialize Google Autocomplete
/*global google*/ let autocomplete = new google.maps.places.Autocomplete(
document.getElementById("autocomplete"),
options
);
// address.
autocomplete.setFields(["address_components", "formatted_address"]);
// Fire Event when a suggested name is selected
autocomplete.addListener("place_changed", handlePlaceSelect);
};
return (
<DisplaySearchBar
onSubmit={onSubmit}
handleChange={handleChange}
renderSortByOptions={renderSortByOptions}
where={where}
what={what}
handleScriptLoad={handleScriptLoad}
/>
);
};
export default Search;
DisplaySearch.js
import React, { useContext } from "react";
import PropTypes from "prop-types";
import RestContext from "../context/restaurant/restContext";
//Import React Script Libraray to load Google object
import Script from "react-load-script";
const DisplaySearchBar = ({
renderSortByOptions,
onSubmit,
where,
handleChange,
what,
handleScriptLoad,
}) => {
const restContext = useContext(RestContext);
const { restaurants, clearSearch } = restContext;
const googleUrl = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_API_KEY}&libraries=places`;
return (
<div className="searchBar">
<h1>Where are you going to eat tonigth?</h1>
<div className="searchBar-sort-options">
<ul>{renderSortByOptions()}</ul>
</div>
<form onSubmit={onSubmit} className="searchBar-form">
<div className="searchBar-input">
<Script url={googleUrl} onLoad={handleScriptLoad} />
<input
type="text"
name="where"
placeholder="Where do you want to eat?"
value={where}
onChange={handleChange}
/>
<input
type="text"
name="what"
placeholder="What do you want to eat?"
onChange={handleChange}
value={what}
/>
</div>
<div className="searchBar-submit">
<input
className="myButton button"
type="submit"
name="submit"
value="Search"
></input>
{restaurants.length > 0 && (
<button className="clearButton button" onClick={clearSearch}>
Clear
</button>
)}
</div>
</form>
</div>
);
};
DisplaySearchBar.propTypes = {
renderSortByOptions: PropTypes.func.isRequired,
where: PropTypes.string.isRequired,
handleChange: PropTypes.func.isRequired,
what: PropTypes.string.isRequired,
handleScriptLoad: PropTypes.func.isRequired,
};
export default DisplaySearchBar;
Thanks for your help

Your code is quiet complex. You might be able to save some lines and make your component leaner with one of the npm packages out there. I would suggest you to give https://github.com/hibiken/react-places-autocomplete a shot.
For performance improvements also consider using React's useMemo and useCallback in functional Components https://reactjs.org/docs/hooks-reference.html#usememo

I found the issue.
I didn't give the id "autocomplete" to my input therefore when the event was firing it couldn't reach it.

Related

react-hook-form: Update form data

In my project using react-hook-form to update and create details. There is an issue in the update form, the values are not updating properly, and the code
countryupdate.tsx
import React from 'react'
import { useQuery } from 'react-query'
import { useParams } from 'react-router-dom'
import { useCountryUpdate } from '../api/useCountryUpdate'
import { getDetails, useDetails } from '../api/useDetails'
import { CountryCreateUpdateForm } from '../forms/createupdateForm'
interface data{
id: string,
name: string
}
export const CountryUpdatepage = () => {
const { dataId }: any = useParams()
const { data, isLoading, isError } = useQuery(['details', dataId], () => getDetails(dataId), {
enabled: !!dataId,
});
const { mutateAsync } = useCountryUpdate();
const onFormSubmit = async() =>{
console.log("mutate", {...data})
await mutateAsync({...data, dataId})
}
return (
<div>
<h3>Update Details</h3>
<CountryCreateUpdateForm defaultValues={data} onFormSubmit={onFormSubmit} isLoading={undefined}/>
</div>
)
}
Here, when console the value inside onFormSubmit, it shows the same data in the previous state
createupdateform.tsx
import { useState } from "react"
import { useCountryCreate } from "../api/usecountrycreate"
import { useForm } from "react-hook-form"
export const CountryCreateUpdateForm = ({ defaultValues, onFormSubmit, isLoading }: any) => {
// console.log("name", defaultValues.data.name)
const { register, handleSubmit } = useForm({ defaultValues:defaultValues?.data });
const onSubmit = handleSubmit((data) => {
onFormSubmit(data)
})
return (
<form onSubmit={onSubmit}>
<div>
<label>Name</label>
<input {...register('name')} type="text" name="name" />
</div>
<button type="submit" >submit</button>
</form>
)
}
I am a beginner in react typescript, Please give me suggestions to solve this problem.
in countryupdate.tsx
the data is undefined at the beggining, so defaultValue of form is not updated after that;
it should help:
return (
<div>
<h3>Update Details</h3>
{data?.data && <CountryCreateUpdateForm defaultValues={data} onFormSubmit={onFormSubmit} isLoading={undefined}/>
}
</div>
)

React js useState&useEffect array duplicates elements after a change

I am a beginner in react js programming. I'm trying to do the todo project, which is a classic project. When I delete or add an element from the list, the newly formed list appears on the screen by combining with the previous one, I will show it with a picture below. I did not understand the source of the eror so wanted to post it here to get some advices suggestions about why it is happening.Thank you.(I am getting and storing data in firebase firestore database)
Before Adding an element initial array state
After adding an element to the array.
I am using useState for array and using useEffect to get initial data
MainPage.js that contains form and the list components.
const MainPage = () => {
const [isLoading, setLoding] = useState(true);
const [array, setArray] = useState([]);
const sub = async (email) => {
var result = [];
await onSnapshot(doc(db, "users", email), (doc) => {
var data = doc.data().todos;
data.forEach((element) => {
Object.keys(element).map(() => {
result.push(element["title"]);
});
});
setArray(result);
setLoding(false);
});
};
useEffect(() => {
sub(auth.currentUser.email);
}, []);
const onAddToDo = (todoTitle) => {
setArray((prevAray) => {
return [...prevAray, todoTitle];
});
};
const onRemove = (title) => {
setArray((prevAray) => {
return [array.pop(array.indexOf(title))];
});
};
return (
<div>
{isLoading && <h1>Loading</h1>}
{!isLoading && (
<div>
<section>
<NavBar></NavBar>
<ToDoForm passData={onAddToDo} />
</section>
<section>
<CardList removeCards={onRemove} array={array} />
</section>
</div>
)}
</div>
);
};
export default MainPage;
Firebase.js that stores the firebase update methods
export const deleteItem = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayRemove({ title: title }),
});
};
export const addnewTodo = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayUnion({ title: title }),
});
};
TodoForm.js component
const ToDoForm = (props) => {
const [todoTitle, setTitle] = useState("");
const titleChangeHandler = (event) => {
setTitle(event.target.value);
};
const newTodoAdder = (event) => {
event.preventDefault();
addnewTodo(todoTitle);
props.passData(todoTitle);
};
return (
<div className="form_holder">
<div className="form_container">
<form onSubmit={newTodoAdder}>
<h3>Add Events</h3>
<label>Title</label>
<input
onChange={titleChangeHandler}
type="text"
placeholder="Title"
id="title"
></input>
<div className="holder">
<button type="sumbit">Add</button>
</div>
</form>
</div>
</div>
);
};
export default ToDoForm;
CardList.js component
const CardList = (props) => {
const array = props.array;
if (array.length === 0) {
return (
<div className="grid_container">
<h2>Found no todos</h2>
</div>
);
}
return (
<div className="grid_container">
{array.map((element, index) => {
return (
<Card
removeSelf={() => {
props.removeCards(element);
}}
key={index}
title={element}
/>
);
})}
</div>
);
};
export default CardList;
Card.js component
const Card = (props) => {
const handleRemove = (event) => {
event.preventDefault();
deleteItem(props.title);
props.removeSelf();
};
return (
<div className="card">
<h2 className="card__title">{props.title}</h2>
<button type="button" onClick={handleRemove}>
Delete
</button>
</div>
);
};
export default Card;
EDIT ;
Index.js file
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
SOLUTION
I fixed the issue by changing the add and remove functions that were inside of MainPage.js file You can see the new versions bellow. Hope someday it will help somebody.
Use effect was called once all I had to do get the data again after a change...
New Remove and Add functions
const onAddToDo = (todoTitle) => {
console.log(todoTitle + " Added");
sub(auth.currentUser.email);
};
const onRemove = (title) => {
console.log(title + " Deleted");
sub(auth.currentUser.email);
};

How to implement 2-way bindings in React?

I understand that we can pass props from a parent component to the child component. In my case, I am passing from App.js ( parent component) to child component - UserInput.js. However, I also want to pass the props back from UserInput.js to App.js so that I can reset the label values.
However, I am unable to do so. When I do props.reset(), it says reset is not a function. I get exception - typeError- "prop.reset is not a function". My ultimate goal is to reset the userName and age field to blank.
Here is my App.js and UserInput.js
import "./App.css";
import UserInput from "./Components/UserInput/UserInput";
import Validation from "./Components/Validation/Validation";
import { useState } from "react";
import Output from "./Components/Output/Output";
const App = (props) =>
{
const errorMessage = [
"",
"Please enter a valid name and age (non-empty values).",
"Please enter a valid age (>0)."
];
let usrList = [
];
const [errNumber, setErrNumber] = useState(0);
const [userList, setUsrList] = useState(usrList);
const validationOkHandler = () => {
//setting error back to 0.
setErrNumber(0);
}
const userInputHandler = (userData) => {
//console.log(userData);
if (userData.user.trim() === "" || userData.age === 0) {
console.log(
"Invalid Input - Please enter a valid name and age (non-empty values)."
);
setErrNumber(1);
}
else if (userData.age < 0) {
console.log("Invalid input Please enter a valid age (>0).");
setErrNumber(2);
}
else {
props.reset();
setErrNumber(0);
userData.id = Math.random();
setUsrList( (previousUSer) => {
return [userData, ...previousUSer];
});
}
};
return (
<div>
<UserInput
onUserInput={userInputHandler}
valdiationOk={validationOkHandler}
/>
{errNumber > 0 && (
<Validation valdiationOk={validationOkHandler}>
{errorMessage[errNumber]}
</Validation>
)}
{errNumber === 0 && <Output userList={userList} />}
</div>
);
}
export default App;
Here is my UserInput.js
import { useState } from "react";
import styles from "./UserInput.module.css";
import App from "../../App";
const UserInput = (props) => {
const [usrName, setUsrName] = useState("");
const [age, setAge] = useState("");
const userNameHandler = (event) => {
console.log(event.target.value);
setUsrName(event.target.value);
};
const ageHandler = (event) => {
console.log(event.target.value);
setAge(event.target.value);
};
const resetForm = () => {
setUsrName('');
};
const addUserHandler = (event) => {
const userInfo = {
user: usrName,
age: +age,
};
console.log(userInfo);
event.stopPropagation();
props.onUserInput(userInfo);
<App reset={resetForm} />
};
const ModalHandler = () => {
console.log("Modal handler clicked");
props.valdiationOk();
};
return (
<div className={`${styles["container"]}`} onClick={ModalHandler}>
<div className={`${styles["inputWrapper"]}`}>
<label htmlFor="username">Username</label>
<input
type="text"
name="username"
id="username"
onChange={userNameHandler}
></input>
</div>
<div className={`${styles["inputWrapper"]}`}>
<label htmlFor="age">Age</label>
<input type="text" name="age" id="age" onChange={ageHandler}></input>
</div>
<div>
<button type="submit" onClick={addUserHandler}>
Add User
</button>
</div>
</div>
);
};
export default UserInput;
There's no need for props to invoke a function from another functional component in this case. You can plainly pass the resetForm as a callback argument to the onUserInput prop function along with userInfo, this argument function can then be directly invoked based on your checks and conditions. If you implement this you might still not see the form fields reset to empty strings "" when the form is valid, because there's no two-way binding established between the state variable and the input fields value. To get things work as expected, you should establish this binding by setting the fields value to the hook's state variable, so that the value in the field is the same as the value of the state variable.
Your updated code should be something like this:
App.js
import "./App.css";
import UserInput from "./Components/UserInput/UserInput";
import Validation from "./Components/Validation/Validation";
import { useState } from "react";
import Output from "./Components/Output/Output";
const App = (props) =>
{
const errorMessage = [
"",
"Please enter a valid name and age (non-empty values).",
"Please enter a valid age (>0)."
];
let usrList = [
];
const [errNumber, setErrNumber] = useState(0);
const [userList, setUsrList] = useState(usrList);
const validationOkHandler = () => {
//setting error back to 0.
setErrNumber(0);
}
const userInputHandler = (userData,reset) => { //reset is the callback function
//console.log(userData);
if (userData.user.trim() === "" || userData.age === 0) {
console.log(
"Invalid Input - Please enter a valid name and age (non-empty values)."
);
setErrNumber(1);
}
else if (userData.age < 0) {
console.log("Invalid input Please enter a valid age (>0).");
setErrNumber(2);
}
else {
reset(); //Call reset function from the function argument as a callback function
setErrNumber(0);
userData.id = Math.random();
setUsrList( (previousUSer) => {
return [userData, ...previousUSer];
});
}
};
return (
<div>
<UserInput
onUserInput={userInputHandler}
valdiationOk={validationOkHandler}
/>
{errNumber > 0 && (
<Validation valdiationOk={validationOkHandler}>
{errorMessage[errNumber]}
</Validation>
)}
{errNumber === 0 && <Output userList={userList} />}
</div>
);
}
export default App;
UserInput.js
import { useState } from "react";
import styles from "./UserInput.module.css";
const UserInput = (props) => {
const [usrName, setUsrName] = useState("");
const [age, setAge] = useState("");
const userNameHandler = (event) => {
console.log(event.target.value);
setUsrName(event.target.value);
};
const ageHandler = (event) => {
console.log(event.target.value);
setAge(event.target.value);
};
const resetForm = () => {
setUsrName('');
};
const addUserHandler = (event) => {
const userInfo = {
user: usrName,
age: +age,
};
console.log(userInfo);
event.stopPropagation();
props.onUserInput(userInfo,resetForm); //Pass resetfrom function as the callback function.
};
const ModalHandler = () => {
console.log("Modal handler clicked");
props.valdiationOk();
};
return (
<div className={`${styles["container"]}`} onClick={ModalHandler}>
<div className={`${styles["inputWrapper"]}`}>
<label htmlFor="username">Username</label>
<input
type="text"
name="username"
id="username"
value = {usrName} //Two way binding using the value attribute.
onChange={userNameHandler}
></input>
</div>
<div className={`${styles["inputWrapper"]}`}>
<label htmlFor="age">Age</label>
<input type="text" value={age} name="age" id="age" onChange={ageHandler}></input>
</div>
<div>
<button type="submit" onClick={addUserHandler}>
Add User
</button>
</div>
</div>
);
};
export default UserInput;

React.js updates some part of DOM nodes when I'm deleting only one element

As I know React creates a new Virtual DOM and compares it with previous one, then it updates Browser DOM with least number of changes possible without rendering the entire DOM again. (in short)
In React documentation I have also read how key should work.
for demonstrating this, I have created todo app, but when I'm deleting one element, all previous elements are re-rendered again (except if I'm not deleting recently added element)
Here is screenshot:
(In Chrome developer tool paint flashing is active for showing renders)
My questions:
Why do previous elements re-render again?
Why key could not fix this problem?
Here is the entire code:
TodoApp.jsx
import React, { useState } from 'react';
import Input from './Input';
import List from './List';
const TodoApp = () => {
const [inputText, setInputText] = useState('');
const [todos, setTodos] = useState([]);
const onSubmitChange = e => {
e.preventDefault();
setTodos([
...todos,
{ text: inputText, completed: false, id: Math.random() * 1000 },
]);
setInputText('');
};
const onChangeEvent = e => {
setInputText(e.target.value);
};
return (
<div>
{todos.map(todo => {
return (
<List todos={todos} setTodos={setTodos} todo={todo} key={todo.id} />
);
})}
<Input
onSubmit={onSubmitChange}
onChange={onChangeEvent}
inputText={inputText}
/>
</div>
);
};
export default TodoApp;
List.jsx
import React from 'react';
import "../todolist/css/TodoApp.css"
const List = ({ todo, todos, setTodos }) => {
const deleteHandle = () => {
setTodos(todos.filter(el => el.id !== todo.id));
};
const completeHandle = () => {
setTodos(
todos.map(el => {
if (el.id === todo.id) {
return { ...el, completed: !el.completed };
}
return el;
})
);
};
return (
<div className={`${todo.completed ? 'completed' : ''}`}>
<div>{todo.text}</div>
<div className="btns">
<button onClick={deleteHandle} className="btn btn-delete">
Delete
</button>
<button onClick={completeHandle} className="btn btn-complete">
complete
</button>
</div>
</div>
);
};
export default List;
Input.jsx
import React from 'react';
const Input = ({ onSubmit, onChange, inputText }) => {
return (
<form onSubmit={onSubmit}>
<input
onChange={onChange}
value={inputText}
type="text"
name="myInput"
autoComplete="off"
/>
</form>
);
};
export default Input;

Need help filtering an autocomplete array with react v17

Most tutorials I found on this subject were outdated. So here I am.
What I expect to do with this app is, input text into the field and filter the results based on what you input. Currently I'm stuck and I've been through so many array methods such as filter, indexOf etc. I'm sure I am overthinking the issue so I need help. Here's the code I have currently:
import React, { useState, useEffect } from "react";
import axios from "axios";
const ITEMS_API_URL = "https://restcountries.eu/rest/v2/all";
function Autocomplete() {
const [countryArr, setCountryArr] = useState([]);
useEffect(() => {
axios.get(ITEMS_API_URL).then((res) => {
setCountryArr(() => {
let arr = res.data;
arr.splice(10, arr.length);
return arr;
});
console.log(countryArr);
});
}, []);
const onChange = e => {
const inputValue = e.target.value
const filteredSuggestions = countryArr.find(arr => arr.name == inputValue)
setCountryArr(filteredSuggestions)
}
return (
<div className="wrapper">
<div className="control">
<input type="text" className="input" onChange={onChange} />
</div>
<div className="list is-hoverable" />
{countryArr.map((country) => {
return (
<ul key={country.numericCode}>
{country.name}
</ul>
)
})}
</div>
);
}
export default Autocomplete;
You should not change to actual data source (countryArr) otherwise it reset and store last filtered on that. so I create state variable filteredCountryArr for filter and setting up filtered valued on that.
import React, { useState, useEffect } from "react";
import axios from "axios";
const ITEMS_API_URL = "https://restcountries.eu/rest/v2/all";
function Autocomplete() {
const [countryArr, setCountryArr] = useState([]);
const [filteredCountryArr, setFilteredCountryArr] = useState([]);
useEffect(() => {
axios.get(ITEMS_API_URL).then(res => {
setCountryArr(() => {
let arr = res.data;
arr.splice(10, arr.length);
return arr;
});
console.log(countryArr);
});
}, []);
const onChange = e => {
const inputValue = e.target.value;
const filteredSuggestions = countryArr.find(arr => arr.name == inputValue);
setFilteredCountryArr(filteredSuggestions);
};
return (
<div className="wrapper">
<div className="control">
<input type="text" className="input" onChange={onChange} />
</div>
<div className="list is-hoverable" />
{filteredCountryArr && filteredCountryArr.length > 0 && filteredCountryArr.map(country => {
return <ul key={country.numericCode}>{country.name}</ul>;
})}
</div>
);
}
export default Autocomplete;

Categories

Resources