I have created two inputs and i update the state using setState. When i type in the input and then console.log it doesn't log it straight away.
For example if i type "a n" into the input it will console log the "a" on the second key stroke...
I know that this.setState doesn't update straight away and batch updates the state, but when i have done this in the past it has worked. Why isn't my input value updating with React?
How can i get my inputs to update straight away? I want this to happen as i need to use the input by the user as a search keyword and pass each input value to a function.
code:
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
movieTitleSearch: "",
searchByYear: "",
};
}
handleOnChange = (event) => {
const { movieTitleSearch } = this.state;
const { searchByYear } = this.state;
this.setState({
...this.state, [event.target.name]: event.target.value,
},
() => {
if (movieTitleSearch || searchByYear > 1) {
this.props.movieSearch(movieTitleSearch, searchByYear);
}
console.log(movieTitleSearch) // does not update straight away in the console.
console.log(searchByYear) // does not update straight away in the console.
}
);
};
render() {
return (
<div>
<label>search film title</label>
<input
onChange={this.handleOnChange}
type="text"
name="movieTitleSearch"
placeholder="search by film"
value={this.state.movieTitleSearch}
/>
<label>search by year</label>
<input
onChange={this.handleOnChange}
type="text"
name="searchByYear"
placeholder="search by year"
value={this.state.searchByYear}
/>
</div>
);
}
}
UPDATED ONCHANGE FUNCTION
handleOnChange = (event) => {
const { movieTitleSearch } = this.state;
const { searchByYear } = this.state;
this.setState(
{
...this.state, [event.target.name]: event.target.value,
},
);
this.props.movieSearch(this.state.movieTitleSearch, this.state.searchByYear)
console.log(this.state.movieTitleSearch) // why when i type in the inputs does this not log to the console straight away?
console.log(this.state.searchByYear)
};
You are accessing the previous state when you use const movieTitleSearch.
You have to use it like this.state.movieTitleSearch, then you will get the updated state value.
handleOnChange = (event) => {
const { movieTitleSearch } = this.state;
const { searchByYear } = this.state;
this.setState({
...this.state, [event.target.name]: event.target.value,
},
() => {
if (movieTitleSearch || searchByYear > 1) {
this.props.movieSearch(this.state.movieTitleSearch, this.state.searchByYear);
}
console.log(this.state.movieTitleSearch)
console.log(this.state.searchByYear)
}
);
};
Also, I would suggest calling the movieSearch outside of the setState callback, because you already have its value with you inside event.target.value
Related
I am trying to build a simple weather application. I need to get an input from the user and then submit it. The following code below doesn't work as required. Can someone please help as I am beginner
class App extends Component {
state = {
search: ''
}
inputSubmitHandler = (e) => {
this.setState({
search: e.target.value
})
}
render() {
return (
<div>
<form >
<input onChange={this.inputSubmitHandler}/>
<button type='submit'>Submit</button>
</form>
<Weather search={this.state.search}/>
</div>
)
}
}
I want to get the full input only after the user clicks submit, and then change the state and then pass it to the Weather component.
Edit: Here is the Weather component to make things more clear
class Weather extends Component {
state = {
temp: null,
humidity: null,
}
Getweather = (search) => {
axios.get('https://cors-anywhere.herokuapp.com/http://api.weatherapi.com/v1/current.json?key=d0c1e9b30aef451789b152051200907&q='+search)
.then(res => {
const tempr = res.data.current.temp_c;
const humidity = res.data.current.humidity;
this.setState({
temp: tempr,
humidity: humidity,
})
// console.log(res);
})
}
render() {
this.Getweather(this.props.search)
return (
<div>
{this.state.temp}
{this.state.humidity}
</div>
)
}
}
If i understand you correctly, you need two different variables: (1) the input variable (search) and then a buffered variable which is filled when the button is clicked:
class App extends Component {
constructor() {
this.state = { search: '', clippedSearch: null }
}
onChange(ev) {
this.setState({ search: ev.target.value })
}
onSubmit(ev) {
this.setState({ clippedSearch: this.state.search })
}
render() {
return <div>
<form>
<input type="text" value={this.state.search} onChange={onChange} />
<button onClick={onSubmit}>Submit</button>
<Weather search={this.state.clippedSearch} />
</form>
</div>
}
}
Your this code is fine:
handleChange = (e) => {
this.setState({ search: e.target.value }); // This will update the input value in state
}
and then you can try:
<form onSubmit={this.submitHandler}>
and define the submitHandler like:
submitHandler = (e) => {
e.preventDefault(); // It will hold the form submit
console.log('state:', this.state.search);
// You will get the updated state ( the one that yo have updated on onChange event listener ) here, make your api call here with the updated state
}
Issue:
<Weather search={this.state.search}/>
Here you are passing the state instantly and that's why it starts sending requests continuously.
I am trying to send user input in a form component to the component managing the state. I tried to use a callback but my input is not being sent.
Tried to use the form object in a callback
//here is the form component
class ListForm extends React.Component {
constructor(props) {
super(props);
this.state = {
NewItem: {
itemText: "",
id: Date.now(),
completed: false
}
};
}
handleChanges = e => {
this.setState({...this.state,
NewItem: { ...this.state.NewItem, [e.target.name]: e.target.value }
});
};
submitItem = e => {
this.setState({ itemText: "" });
this.props.addItem(e, this.state.NewItem);
};
render() {
return (
<form onSubmit={this.submitItem}>
<input
type="text"
value={this.state.NewItem.itemText}
name="itemText"
onChange={this.handleChanges}
/>
<button>Add</button>
</form>
);
}
}
export default ListForm;
addItem is in the parent component and looks like this. Groceries is just an array of objects with itemText, id and completed
addItem = (e, item) => {
e.preventDefault();
this.setState({
...this.state,
groceries: [...this.state.groceries, item]
});
};
I can enter in the form, but when I hit enter, nothing is added to the list
Very first thing to try is to add e.persist to your addItem() function
addItem = (e, item) => {
e.preventDefault();
e.persist()
this.setState({
...this.state,
groceries: [...this.state.groceries, item]
});
};
Another option is using redux to save the form data in the Global state and then accessing it in the parent component through redux. Here is an example of what that might look like:
import React, { Component } from 'react';
import * as ACTIONS from '../store/actions/actions';
import { connect } from 'react-redux';
class Form1 extends Component {
state ={
value: ''
}
handleChange = (event) => (
this.setState({value: event.target.value})
)
handleSubmit = (event) => {
event.preventDefault()
this.props.input_action_creator(event.target.name.value)
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input id="name" onChange={this.handleChange} type="text" />
<button type="submit"> Submit </button>
</form>
<br />
</div>
)}
}
function mapDispatchToProps(dispatch) {
return {
input_action_creator: (text) => dispatch(ACTIONS.user_input(text))
}
}
export default connect(mapDispatchToProps)(Form1);
class Parent extends Component {
access form data with
this.props.user_Text
}
function mapStateToProps(state) {
return {
user_text: state.user_reducer.user_text
}
}
here is a fully funcitoning react-redux project if you are interested
https://github.com/iqbal125/modern-react-app-sample
I'm trying to get data from a props.children[Input] to it's parent[Form], to build an all in one form component. I am currently stuck at getting data to Input component to Form component.
This is what i have done so far =>
edit.js =>
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {
formFields: {
name: '',
email: ''
}
}
}
render() {
return (
<Form
id={ this.props.match.params.id }
onReceiveFormData={ this.onFiledDataChange.bind(this) }
>
<Input
label='name'
name='name'
value='some'
/>
<Input
label='email'
name='email'
value='email#gmail'
/>
</Form>
);
}
}
Input.js =>
export default class Input extends Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value
}
}
static setValue(val) {
return val ? val : ''
}
handleInputChange = (event) => {
this.setState({
value : event.target.value
})
this.props.onInputChange({
[event.target.name] : event.target.value
})
}
render() {
return (
<input
name={ this.props.name }
value={ Input.setValue(this.state.value) }
/>
);
}
}
Form.js =>
export default class Form extends Component {
constructor(props) {
super(props)
}
saveClick() {
/**
Save logic => I want to get the field data here
**/
}
render() {
return (<div>
{
this.props.children
}
<Button
onClick={ () => this.saveClick() }
>Save</Button>
</div>
);
}
}
For example, If post -> edit page and user -> edit page =>
I would like to do setting the input value inside the Form.js, Form.js will be responsible for setting the values inside those Inputs when it is mounted and it will be responsible for sending the data to the server.
I would like to the know, the scenario I have suggest is possible or is there a better way to do it?
Invert control (make Input a controlled component). Keeping state inside components unnecessarily is generally a bad design practice. Look at the existing <input> it takes a value and an onChange. Your component should do too.
const Input = ({
className,
...rest
}) => (
<input
className={classNames('Input', className)}
type="text"
{...rest}
/>
);
Then the parent has the state for when the form is submitted in Edit.
onFormSubmit = e => {
e.preventDefault();
console.log(this.state.name, this.state.email);
}
onChange = name => e => this.setState({ [name]: e.target.value })
// ...
<Input onChange={this.onChange('name')} value={this.state.name} />
<Input onChange={this.onChange('email')} value={this.state.email} />
If you want Form to listen in on state changes you could do this:
onChange = (e, originalOnChange) => {
console.log('onChange called:', e.target.value);
originalOnChange && originalOnChange(e);
}
// ...
const children = React.Children.map(this.props.children, child =>
React.cloneElement(child, { onChange: e => this.onChange(e, child.props.onChange) })
);
It's a bit unorthodox but technically achievable.
I'm having trouble setting the state of a component in React. The component is called "Search" and uses react-select. The full component is here:
class Search extends React.Component {
constructor(props){
super(props);
let options = [];
for (var x in props.vals){
options.push({ value: props.vals[x], label: props.vals[x], searchId: x });
};
this.state = {
inputValue: '',
value: options
};
}
handleChange = (value: any, actionMeta: any) => {
if(actionMeta.action == "remove-value"){
this.props.onRemoveSearch({ searchId: actionMeta.removedValue.searchId })
}
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleSearch = ({ value, inputValue }) => {
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)], // Eventually like to take this out...
});
this.props.onSearch({ inputValue });
}
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.handleSearch({
value,
inputValue
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
className={"tags"}
components={components}
inputValue={inputValue}
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Add filters here..."
value={value}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
You've probably noticed the strange thing that I'm doing in the constructor function. That's because I need to use data from my firebase database, which is in object form, but react-select expects an array of objects
with a "value" and "label" property. Here's what my data looks like:
To bridge the gap, I wrote a for-in loop which creates the array (called options) and passes that to state.value.
The problem: Because I'm using this "for in" loop, React doesn't recognize when the props have been changed. Thus, the react-select component doesn't re-render. How do I pass down these props (either modifying them inside the parent component or within the Search component) so that the Search component will re-render?
I would suggest not using the value state. What you do is simply copying props into your state. You can use props in render() method directly.
I reckon you use the value state because you need to update it based on user actions. In this case, you could lift this state up into the parent component.
class Parent extends React.Component {
constructor() {
this.state = { value: //structure should be the same as props.vals in ur code };
}
render() {
return (
<Search vals={this.state.value}/>
);
}
}
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
inputValue: '',
};
}
render() {
const { inputValue } = this.state;
const { vals } = this.props;
let options = [];
for (var x in vals){
options.push({ value: vals[x], label: vals[x], searchId: x });
};
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
value={options}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
class CreateEventForm extends Component {
constructor(props) {
super(props)
this.state = {
fields: {
name: '',
location: '',
description: '',
datetime: '',
},
friendsInEvent: [],
},
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const { checked, value } = e.target
let { friendsInEvent } = { ...this.state }
if (checked) {
friendsInEvent = [...friendsInEvent, value]
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
this.setState({ friendsInEvent }, () => console.log(this.state))
}
render() {
const { friends, createEvent, addFriendsToEvent } = this.props
const { fields: { name, location, description, datetime } } = this.state
const setter = propName => event => {
const nextFields = { ...this.state.fields }
nextFields[propName] = event.target.value
this.setState({ fields: nextFields })
}
return (
<div {...cssForm}>
<div>
<Label label="Event Name" />
<Field onChange={setter('name')} />
<Label label="Event Date/Time" />
<Field onChange={setter('datetime')} type="datetime-local" />
<Label label="Event Location" />
<Field onChange={setter('location')} />
<Label label="Event Description" />
<Field onChange={setter('description')} />
<SubHeader title="Add Friends to the Event..." />
<div>
{friends
.mapEntries(([friendId, frn]) => [
friendId,
<div key={friendId}>
<input
value={friendId}
type='checkbox'
onChange={this.handleChange}
defaultChecked={false} />
{frn.firstName} {frn.lastName}
</div>,
])
.toList()}
</div>
<Link to="/">
<Button style="blue" name="Done" onClick={() => createEvent(this.state.fields)} />
</Link>
</div>
</div>
)
}
}
export default CreateEventForm
This code above works and stores all of the correct values as expected. However I want to move friendsInEvent[ ] inside fields{ }. To look like:
super(props)
this.state = {
fields: {
name: '',
location: '',
description: '',
datetime: '',
friendsInEvent: [],
},
},
this.handleChange = this.handleChange.bind(this);
}
How can I achieve this? Everything I have tried breaks the handleChange(e) function or overwrites fields{ } with the friendsInEvent array.
I assume I have to change the values inside the handleChange(e) function after I move the array inside this.state.field?
Also, is there a way to merge my two functions setter and handleChange(e)? I kept them separate as they handle two different types (string and array).
Thanks.
From the looks of it you'll need to make the following changes to handleChange:
handleChange(e) {
const { checked, value } = e.target;
// Ensure that you're destructuring from fields
let { friendsInEvent } = { ...this.state.fields };
if (checked) {
friendsInEvent = [...friendsInEvent, value];
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
// You need to take a copy of state, and then supply a new copy
// of the the fields property which takes a copy of the old fields
// property, and overwrites the friendsInEvent with the new data
this.setState({
...this.state,
fields: {...this.state.fields, friendsInEvent }
}, () => console.log(this.state));
}
Here's a small React-free example of how this would work.
Why can't you do it in the below way.
handleChange(e) {
const { checked, value } = e.target
let { friendsInEvent } = { ...this.state }
if (checked) {
friendsInEvent = [...friendsInEvent, value]
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
let object = {};
object.name = this.state.name;
object.location = this.state.location;
object.description = this.state.description;
object.datetime = this.state.datetime;
object.friendsInEvent = friendsInEvent;
this.setState({
fields: object
})
}
Never do setState inside render. You are not suppose to do setState inside render. Event handlers should be handled before render method but not inside of render. Try avoiding doing setState inside render.
const setter = propName => event => {
const nextFields = { ...this.state.fields }
nextFields[propName] = event.target.value
this.setState({ fields: nextFields })
}