So, I am building a book finder in React using Google Books API.
The user types the name of the book he/she wants to search, in the input field, and the value typed in the input is appended to the end of the API url.
Basically, the API is called every single time the user types something in the input field, because I want display some of the results in a dropdown below the search bar. The problem is that, if the user hits spacebar, which is an empty string, I get a HTTP 400 error, specifically this:
Error: Request failed with status code 400
at createError (createError.js:17)
at settle (settle.js:19)
at XMLHttpRequest.handleLoad (xhr.js:60)
If I call .trim() on the input value, then that just prevents a user from typing anything at all. I'm kind of confused what to do right now. Also, is calling the API everytime the input value changes an expensive operation? This is what I've tried so far:
import React, { Component } from 'react';
import axios from 'axios';
export default class BookSearchForm extends Component {
state = {
searchTerm: ''
};
fetchBooks = () => {
let apiURL = `https://www.googleapis.com/books/v1/volumes`;
axios
.get(`${apiURL}?q=${this.state.searchTerm}`)
.then(res => console.log(res))
.catch(err => console.log(err));
};
onChange = e => {
this.fetchBooks();
this.setState({ searchTerm: e.target.value });
};
render() {
return (
<div className="container">
<form autoComplete="off">
<input
className="search-bar"
type="search"
placeholder="Search for books"
onChange={this.onChange}
value={this.state.searchTerm}
/>
</form>
</div>
);
}
}
You can replace whitespaces using the \s regex.
You may also want to fetch after the state is updated (callback of setState) :
onChange = e => {
this.setState(
{ searchTerm: e.target.value.replace(/\s/g, '') },
() => {
this.fetchBooks();
}
);
};
.replace(/\s/g, '') will replace all whitespaces (including tab, newline etc...) from the string by an empty char (''), making whitespaces from user inputs doing nothing.
You could validate the user input and don't allow empty strings.
Also, is calling the API everytime the input value changes an expensive operation?
Likely yes. You might want to debounce or throttle your requests
You can try below code.
onChange = e => {
let value = e.target.value;
this.fetchBooks(value.trim());
this.setState({ searchTerm: e.target.value });
};
fetchBooks = (str) => {
if (str) {
let apiURL = `https://www.googleapis.com/books/v1/volumes`;
axios
.get(`${apiURL}?q=${str}`)
.then(res => console.log(res))
.catch(err => console.log(err));
}
};
Yes, calling API on every input change is expensive. You should use debouncing.
I think the code should be:
onChange = async (e) => {
await this.setState({ searchTerm: e.target.value });
this.fetchBooks();
};
Related
I'm using Antd Input library, whenever I type in the start or in the middle of the word my cursor jumps to the end.
const handleOpenAnswer =( key, value )=>{
handleFieldChange({
settings: {
...settings,
[key]: value
}
})
}
return (
<Input
required
size='default'
placeholder='Label for Diference Open Answer Question'
value='value'
onChange={({ target: { value } }) => {
handleOpenAnswer('differenceOpenAnswerLabel', value)
}}
/>
The reason why your cursor always jumps to the end is because your parent component gets a new state and therefore re-renders its child components. So after every change you get a very new Input component. So you could either handle the value change within the component itself and then try to pass the changed value up to the parent component after the change OR (and I would really recommend that) you use something like React Hook Form or Formik to handle your forms. Dealing with forms on your own can be (especially for complex and nested forms) very hard and ends in render issues like you face now.
Example in React-Hook-Form:
import { FormProvider, useFormContext } = 'react-hook-form';
const Form = () => {
const methods = useForm();
const { getValues } = methods;
const onSubmit = async () => {
// whatever happens on submit
console.log(getValues()); // will print your collected values without the pain
}
return (
<FormProvider {...methods}>
<form onSubmit={(e) => handleSubmit(onSubmit)(e)>
{/* any components that you want */}
</form>
</FormProvider>
);
}
const YourChildComponent = () => {
const { register } = useFormContext();
return (
<Input
{...register(`settings[${yourSettingsField}]`)}
size='default'
placeholder='Label for Diference Open Answer Question'
/>
)
}
I am building a simple blog app and I am trying to update title of the blog But it is not updating, it is just showing the current state.
I have tried many times by changing the method of setting state but it is still showing that error.
App.js
function BlogDetail() {
const [blogName, setBlogName] = useState("");
axios.get("/api/blog_detail/70/").then(res => {
setBlogName(res.data[0].blog_name)
})
const handleChange = (e) => {
console.log(e.target.value)
setBlogName({
...blogName,
[e.target.name]: e.target.value
})
}
const saveBlog = (e) => {
// sending to API
console.log(blogName)
}
return (
<div>
<form>
{blogName}
<input type="text" name="blogName" value={blogName} onChange={e => handleChange} />
<button type="submit" onClick={e => saveBlog(e)}>Save</button>
<form>
</div>
)
}
And When I update on change instead of updating on submit
onChange=(e => setBlogName(e.target.value))
Then it is showing
A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined
I have tried many times but it is still not working.
input requires a string as a value, but you are trying to pass an object:
setBlogName({
...blogName,
[e.target.name]: e.target.value
})
instead pass a string:
setBlogName(e.target.value)
Also, you need to execute handleChange function and pass the event param.
onChange={e => handleChange(e)}
Edit:
Looked at it second time and it should be like this:
function BlogDetail() {
const [blogName, setBlogName] = useState("");
// without this you override state every time you press a key
useEffect(() => {
axios.get("/api/blog_detail/70/").then(res => {
setBlogName(res.data[0].blog_name)
})
}, [])
const handleChange = (e) => {
// just use value here
setBlogName(e.target.value)
}
const saveBlog = (e) => {
// sending to API
console.log(blogName)
}
return (
<div>
<form>
{blogName}
{ /* remember to run the function */ }
<input type="text" name="blogName" value={blogName} onChange={e => handleChange()} />
<button type="submit" onClick={e => saveBlog(e)}>Save</button>
<form>
</div>
)
}
Besides the problem that within handleChange you need to pass an an string value to setBlogName you also need to wrap your axios fetch call in a useEffect.
The problem is that everytime you trigger a rerender while calling setBlogName you are calling your API point again and set the value back to the fetched value.
You should prevent that by doing the following ->
useEffect(() => {
axios.get("/api/blog_detail/70/").then(res => {
setBlogName(res.data[0].blog_name)
}), [])
Don't forget to install { useEffect } from 'react'.
And well of course update handleChange ->
const handleChange = (e) => {
const newBlogPostName = e.target.value
console.log(newBlogPostName)
setBlogName(newBlogPostName)
}
you have not any action in this method. where is the update state?
const saveBlog = (e) => {
// sending to API
console.log(blogName)
}
and in this method you change the string to an object
const handleChange = (e) => {
console.log(e.target.value)
setBlogName({
...blogName,
[e.target.name]: e.target.value
})
}
so the problem is that your function updates your state to an object and then you want to display that object(not a string property of that object) in the DOM. its wrong because you cant display objects in the DOM in react. in this case, you even get an error because you cant use spread operator on strings. you cant do something like this: ...("test")
const handleChange = (e) => {
console.log(e.target.value)
//the state value will be an object. its wrong. you even get an error
because of using spread operator on a string
setBlogName({
...blogName //this is a string at the init time,
[e.target.name]: e.target.value
})
}
so whats the solution?
you should update your state to a string or use a string property of the object.
something like this:
const handleChange = (e) => {
console.log(e.target.value)
setBlogName("string")
}
return (<>{blogName}</>)
thats it.
I've been beating my head against the wall and cross referencing code I've read from others and questions that have been asked. I'm at the point where I know I'm just not looking in the right direction, however I'm not sure where to turn.
The app I'm writing was originally written by me in 2019, and I did not know of React h
Hooks as I learned components from a coding bootcamp. TL;DR: I'm rusty.
The issue:
I'm trying to use AXIOS to use a PUT call into my MongoDB, and the way I learned was by using refs. Refs in the way I learned is now deprecated, and I just want to get this working so I can move on to another project and start using Hooks instead.
When I use my button created to save the change, I get an error in the browser console, however it refreshes too fast for me to catch the exact error. The second I get to look at the wall of text, it looks similar to a bad promise, but I'm not entirely sure. Either way, the data does not get updated.
My code:
import React, { Component } from 'react'
import axios from 'axios'
export default class EditSeed extends Component {
constructor(props){
super(props)
this.state = {
_id: '',
seed: '',
created_at: '',
__v: ''
}
this.changeHandler = this.changeHandler.bind(this)
this.myRef = React.createRef
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
componentDidMount(){
this.fetchSeed()
}
fetchSeed = async () => {
try {
const res = await axios.get(`/api/grapevine/${this.props.match.params.id}`)
this.setState({
_id: res.data._id ,
seed: res.data.seed,
created_at: res.data.created_at,
__v: res.data.__v
})
}
catch (err) {
this.setState({ error: err.message })
console.log(err)
}
}
editSeed = async (newSeed) => {
try {
const res = await axios.request({
method: 'put',
url: `/api/grapevine/${this.state._id}`,
data: newSeed
})
res.this.props.history.push(`/`)
}
catch (err) {
this.setState({ error: err.message })
console.log(err)
}
}
onSubmit = (e) => {
const newSeed = {
seed: this.myRef.current.seed.value,
created_at: this.myRef.current.created_at.value
}
this.editSeed(newSeed)
e.preventDefault()
}
changeHandler = (e) => {
const target = e.target
const value = target.value
const name = target.name
this.setState({
[name]: value
})
}
render(){
return(
<div>
<h1>Edit Seed</h1>
<form onSubmit={this.onSubmit.bind(this)}>
<label>
Edit Message:
<input type="text" name="seed" ref={this.myRef} value={this.state.seed} onChange={this.changeHandler} />
</label>
<input type="submit" value="Save" />
</form>
</div>
)
}
}
My fetch function works as intended, it's mainly just the edit that doesn't want to work. I have tried changing
<input type="text" name="seed" ref={this.myRef} value={this.state.seed} onChange={this.changeHandler} />
to
<input type="text" name="seed" ref={this.myRef.current} value={this.state.seed} onChange={this.changeHandler} />
Obviously I am doing something wrong, and I have referenced and read the React docs on this about 10 times and I'm really just hitting a wall.
Any insight would be massively appreciated.
Firstly, React.createRef is a function, so it needs to be invoked.
this.myRef = React.createRef();
Then you simply attach the ref to the element.
<input
ref={this.myRef}
type="text"
name="seed"
value={this.state.seed}
onChange={this.changeHandler}
/>
Then whenever you need to reference the input's ref you access the current value of it.
this.myRef.current
Update
I don't think the React ref is providing much for you here since you duplicate the seed and created_at into local state. You update the seed state via the onChange handler. Why not just forego the ref and access the state in the submit handler. I believe this will resolve your "cannot access value of undefined" error since the input element target object likely doesn't have seed or created_at attributes to access a value of.
onSubmit = (e) => {
e.preventDefault();
const newSeed = {
seed: this.state.seed,
created_at: this.state.created_at // is this supposed to be a current datetime?
}
this.editSeed(newSeed);
}
changeHandler = (e) => {
const { name, value } = e.target;
this.setState({
[name]: value
});
}
You did nearly got it right, just a few minor changes will lead you to the right way:
First: You need to invoke React.createRef function:
this.myRef = React.createRef -> this.myRef = React.createRef()
Second: You need to call e.preventDefault() from the begining of your handler so that the whole page will not get refeshed imediately. Something like this:
onSubmit = (e) => {
e.preventDefault();
const newSeed = {
seed: this.myRef.current.seed.value,
created_at: this.myRef.current.created_at.value
}
this.editSeed(newSeed)
}
From then, I think you will easily get to the right path.
You can use useRef hook in functional components like this:
import React, {useRef} from 'react';
function MyComponenet () {
myRef = useRef(null) // <---- initialize your ref with null
return (
<div ref=myRef >
Hello World
</div>
)
}
now you create a reference and initiate it with a null value, then passed it to the div element. from now you can manipulate your ref like get its value or assign values, click, and other actions.
with TypeScript
myRef = useRef<HTMLDivElement>(null)
if you are passing myRef to a div element, so your ref type will be HTMLDivElement
I have a React form for user login, everything works fine except for a setting a successful or unsuccessful message. Once i login, i set the value of a useState variable [res, setRes] to either successful or usuccessful depending on whether the user is registered or not. Problem is, even if the user is registered and username and password is correct, i get the message "invalid credentials" at least and most once. Subsequent calls from the same user result in the correct message being displayed. I searched and found that state is one step behind, and the solution is to use useEffect, but i am already using it. Can anyone help me figure out what the problem is? Code is as follows
export const Login = () => {
const email = useField('string')
const password = useField('password')
const [cred, setCred] = useState({})
const send = (e:any) => {
e.preventDefault()
setCred({'email':email.value, 'password':password.value})
showToast()
}
const [toastIsShown, setToastIsShown] = useState(false);
const showToast = () => {
setToastIsShown(true);
}
const [res,setRes] = useState('')
const hook = () => {
axios
.post('http://localhost:5000/auth', cred)
.then(response => {
console.log('response is ',response.data)
setRes('Login Successful')
})
.catch(err => {
console.log("error is ",err.response)
setRes('Invalid username or password')
})
}
useEffect(hook,[cred])
return (
<>
<form onSubmit = {send}>
<IonText>Enter Name</IonText>
<br />
<input {...email} />
<br />
<IonText>Enter Password</IonText>
<br />
<input {...password} />
<br />
<button>Send</button>
</form>
<IonToast
isOpen={toastIsShown}
onDidDismiss={() => setToastIsShown(false)}
message={res}
duration={3000}
/>
</>
)
}
I am using Ionic, which is why you see Toast there. Also, the language is Typescript.
Thanks
The useEffect hook is always called when a component mounts, and after that every time a value in its dependency array changes. Since an empty object is presumably not a valid log in, you're always going to get an unsuccessful attempt when the component mounts. You could do some simple validation like:
cred.email && cred.password && axios.post('http://localhost:5000/auth', cred)...
However, the root of the problem is that you are misusing useEffect. A log-in attempt is (usually, and in your case) a one time event, not a side-effect that occurs as the result of previous significant action. The side-effect in this scenario happens after the log-in attempt, when you trigger a Toast which contains a notification about the result:
export const Login = () => {
const email = useField('string');
const password = useField('password');
const [res, setRes] = useState('');
const [toastIsShown, setToastIsShown] = useState(false);
const send = (e:any) => {
e.preventDefault();
const cred = {
email: email.value,
password: password.value
};
axios
.post('http://localhost:5000/auth', cred)
.then(response => {
console.log('response is ',response.data)
setRes('Login Successful');
})
.catch(err => {
console.log("error is ",err.response)
setRes('Invalid username or password');
});
};
useEffect(() => {
res && setToastIsShown(true);
}, [res]);
return (
...
)
}
This is just to demonstrate a more reasonable use of useEffect. In reality I would probably not even use one here and instead just call setToastIsShown from inside send after setting res. A useEffect really comes in handy when you have two correlated pieces of data which are updated by multiple uncorrelated methods.
I am trying to create a search feature using react hooks but it keeps returning the error:
Cannot read Property of Undefined
on the updateSearch function whenever I type in the input field.
const [search, setSearch] = React.useState('');
const [searchResults, setSearchResults] = React.useState([]);
const [state, setState] = React.useState({
open: false,
name: '',
users:[]
});
useEffect(() => {
getAllUsers();
}, []);
const getAllUsers = () => {
fetch('/userinformation/', {
method: 'GET',
headers: {'Content-Type':'application/json'}
})
.then(function(response) {
return response.json()
}).then(function(body) {
console.log(body);
setState({...state, users: body });
})
}
const updateSearch = (event) => {
setSearch(event.target.value)
}
React.useEffect(() => {
const results = state.users.filter(user =>
user.toLowerCase().includes(search)
);
setSearchResults(results);
}, [search]);
return (
<input type="text" value={search} onChange={(e) => updateSearch(e.target.value)}/>
)
Whenever I type in the search bar I get the following error:
How can i fix this?
You can either get to the value of passed event by changing
<input type="text" value={search} onChange={(event) => updateSearch(event}/>
or you can keep the input element as it is and change the update updateSearch callback to
const updateSearch = (event) => { setSearch(event) }
Secondly, you are applying includes to a single item of an array which is specific to array methods, you need to make following changes as well to make it work:
React.useEffect(() => {
const results = state.users.filter( user => user.firstName.toLowerCase() === search );
setSearchResults(results);
}, [search])
in your input you're already passing valueonChange={(e) => updateSearch(e.target.value) and in updateSearch you're trying to accessing it. Change it like this, if you want to access event in updateSearch method and get value from it.
<input type="text" value={search} onChange={(e) => updateSearch(e}/>
I would teach you a secret that has worked very well for me over the years. When javascript gives you such error as cannot read property ***whateverValue*** value of undefined it means you are trying to read the property of an object that does not exist. In this case, the object you're trying to read from is undefined, hence it cannot have any key: value pair.
Back to your question: TypeError: Cannot read property value of undefined
Using cmd+f to check for all places where value is used shows me everywhere you used value on event.target.value
Stay with me (I know this is boring, but it would help later).
You have an event handler named updateSearch.
All you need here now is to change your input tag to this:
<input type="text" value={search} onChange={updateSearch}/>
Don't worry, React would handle the rest, it automatically parses the event to eventHandler which can then be accessed on such eventHandler.
Also, I think you might want to refactor this component.
Something like import React, {useState, useEffect} from React
you won't have to call React.useEffect or React.useState in other parts of the project. just useEffect or useState.
Goodluck :+1
You have already passed the value of the input into the updateSearch method.
This is how you should fix it:
const updateSearch = (value) => {
setSearch(value);
};
And as for the second issue you have raised on your useEffect hook, you will have to call toLowerCase() on one of your properties (either firstName or lastName), depending on what you need to search for.
React.useEffect(() => {
const results = state.users.filter(user =>
user.firstName.toLowerCase().includes(search)
);
setSearchResults(results);
}, [search]);