state hooks is not updating state on change - javascript

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.

Related

How to set a default value in an input tag?

const [userInfo, setUserInfo] = useState([]);
const handleUserInfo = (id) => {
fetch(`https://602e7c2c4410730017c50b9d.mockapi.io/users/${id}`)
.then(res => res.json())
.then(data => setUserInfo(data))
}
<input type="text" defaultValue={userInfo?.profile?.firstName + userInfo?.profile?.lastName} className="form-control bg-light" id="exampleInputName" aria-describedby="name"></input>
I am expecting to see both firstname and last name in that input field with a gap between first name and last name. But I see NAN because I tried to add firstname and lastname using plus (+)
The NAN doesn't show up if I only want to see the first name when the default value is defaultValue={userInfo?.profile?.firstName}
Hello and welcome to Stack Overflow!
Input's defaultValue is set on the initial load of your component. Your request is asynchronous which means that you are probably getting the result after the initial load. So you need to transform your component into a controlled component. Meaning, you will have to create a state that will hold the input value as well as to listen on the onChange event and alter your state to reflect the changes. You can check the official docs here for more info on forms and their behavior.
export default function ExampleComponent() {
const [userInfo, setUserInfo] = React.useState([]);
const [inputValue, setInputValue] = React.useState('');
React.useEffect( () => {
handleUserInfo(5)
}, [])
const handleUserInfo = (id) => {
fetch(`https://602e7c2c4410730017c50b9d.mockapi.io/users/${id}`)
.then(res => res.json())
.then(data => {
setInputValue(`${data?.profile?.firstName} ${data?.profile?.lastName}`)
setUserInfo(data)
} )
}
return (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
className="form-control bg-light"
id="exampleInputName"
aria-describedby="name"
/>
)
}
You have a typo when you are trying to display default value, try to change
userInfo?.profile?.firstName + userInfo?.profile?.lastlName to
userInfo?.profile?.firstName + userInfo?.profile?.lastName.
(lastName was misspelled incorrectly).
also it's a better way to use template literals

Visit each child in props.children and trigger a function

I want to be able to visit the children <Textfield> of my form <Form> upon submit.
In each child hook object, I also want to trigger a certain function (eg., validate_field). Not sure if this possible in hooks? I do not want to use ref/useRef and forwardRef is a blurred concept to me yet (if that's of any help).
My scenario is the form has been submitted while the user did not touch/update any of the textfields so no errors were collected yet. Upon form submit, I want each child to validate itself based on certain constraints.
I tried looking at useImperativeHandle too but looks like this will not work on props.children?
Updated working code in:
https://stackblitz.com/edit/react-ts-jfbetn
submit_form(evt){
props.children.map(child=>{
// hypothetical method i would like to trigger.
// this is what i want to achieve
child.validate_field() // this will return "is not a function" error
})
}
<Form onSubmit={(e)=>submit_form(e)}
<Textfield validations={['email']}>
<Textfield />
<Textfield />
</Form>
Form.js
function submit_form(event){
event.preventDefault();
if(props.onSubmit){
props.onSubmit()
}
}
export default function Form(props){
return (
<form onSubmit={(e)=>submit_form(e)}>
{props.children}
</form>
)
}
So the Textfield would look like this
…
const [value, setValue] = useState(null);
const [errors, setErrors) = useState([]);
function validate_field(){
let errors = []; // reset the error list
props.validations.map(validation => {
if(validation === 'email'){
if(!some_email_format_validator(value)){
errors.push('Invalid email format')
}
}
// other validations (eg., length, allowed characters, etc)
})
setErrors(errors)
}
export default function Textfield(props){
render (
<input onChange={(evt)=>setValue(evt.target.value)} />
{
errors.length > 0
? errors.map(error => {
return (
<span style={{color:'red'}}>{error}</span>
)
})
: null
}
)
}
I would recommend moving your validation logic up to the Form component and making your inputs controlled. This way you can manage the form state in the parent of the input fields and passing in their values and onChange function by mapping over your children with React.cloneElement.
I don't believe what you're trying to do will work because you are trying to map over the children prop which is not the same as mapping over say an array of instantiated child elements. That is to say they don't have state, so calling any method on them wouldn't be able to give you what you wanted.
You could use a complicated system of refs to keep the state in your child input elements, but I really don't recommend doing that as it would get hairy very fast and you can just solve the issue by moving state up to the parent.
simplified code with parent state:
const Form = ({ children }) => {
const [formState, setFormState] = useState(children.reduce((prev, curr) => ({ ...prev, [curr.inputId]: '' }), {}));
const validate = (inputValue, validator) => {}
const onSubmit = () => {
Object.entries(formState).forEach(([inputId, inputValue]) => {
validate(
inputValue,
children.filter(c => c.inputId === inputId)[0].validator
)
})
}
const setFieldValue = (value, inputId) => {
setFormState({ ...formState, [inputId]: value });
};
const childrenWithValues = children.map((child) =>
React.cloneElement(child, {
value: formState[child.inputId],
onChange: (e) => {
setFieldValue(e.target.value, child.inputId);
},
}),
);
return (
<form onSubmit={onSubmit}>
{...childrenWithValues}
</form>
)
};
const App = () =>
<Form>
<MyInput validator="email" inputId="foo"/>
<MyInput validator="email" inputId="foo"/>
<MyInput validator="password" inputId="foo"/>
</Form>
I still don't love passing in the validator as a prop to the child, as pulling that out of filtered children is kinda jank. Might want to consider some sort of state management or pre-determined input list.

Debounce on a React functional component when typing not working

I have a form component, and I would like to perform an API request as I am typing. I want to debounce this so I don't spam the server. Here is my code:
export default function MyItem ({ id, name }) {
const debouncePatch = (name) => {
debounce(patchItem.bind(this, id, name), 500)
}
return (
<div>
<form
type='text'
value={name}
onChange={(evt) => debouncePatch(evt.target.value)}
/>
</div>
)
}
The patch item does a simple patch call to the server so that the item's name updates as I am typing. It looks something like this:
export default function patchItem (id, name,) {
return axios.patch(`${MY_BASE_URL}/${id}`, { name })
}
With the debounce it is not working at all. The patchItem function is never called. How can I fix this?
You're calling debounce on every change to the input but it just creates a new debounced function every time. You need to create a single debounced function and then use it as event handler. Like this:
function MyItem({ id, name }) {
let debouncePatch = debounce((id, name) => {
patchItem(id, name);
}, 500);
// OR SIMPLER
let debouncePatch = _.debounce(patchItem, 500);
return (
<div>
<input
type="text"
defaultValue={name}
onChange={(event) => debouncePatch(id, event.target.value)}
/>
</div>
);
}
Also make sure the input gets the defaultValue rather than value so it can be edited.
There's 2 issues here. Firstly, debounce returns a function, so you need to call that one.
Secondly, if you don't memoize the debounced function then it won't work properly, as each re-render will create a new debounced function, and thus defeat the point of debouncing.
Try something like this:
const debouncePatch = useCallback(debounce(() => patchItem(this, id, name), 500), []);
Make your input controllable,
and you can use simple utile use-debounce
import { useDebounce } from 'use-debounce';
export default function MyItem ({ id, name }) {
const [value, setValue] = useState('')
const [debouncedValue] = useDebounce(value, 1000);
const handleChange = useCallback((e) => {setValue(e.target.value)}, [])
useEffect(() => {
console.log(debouncedValue);
*// here you can place any code what you need. **console.log** will be displayed every second, or any time that you may change in hook above.*
}, [debouncedValue])
return (
<div>
<form
type='text'
value={name}
onChange={handleChange}
/>
</div>
)
}
ps. you do not have to use bind in your example for sure. and I am not sure you can apply onChange event to form

Cannot read property of undefined React Hooks

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]);

How do I prevent empty values in my search bar, in React?

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

Categories

Resources