API call in react from user input - javascript

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.

Related

Input output does not update straight away it is delayed

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

React form sending empty object

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

How to get data from props.children to its parent in ReactJS?

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.

Updating a list in React.js based on whether a new element was added or existing was edited

I have App as my main component and TodoList to render my list of todos. When Add Task button is clicked, it opens an input which autosaves the input that we type. This is saved in a state addTaskInput which is then passed to TodoList component as a prop addItem when the focus on input is removed.
Now, we may edit the same element by simply clicking on it and typing. In that case, I want to update the same component. But if a new item is added, I want to add a new item to list.
1) How to check this?
2) I want to update my todo state inside TodoList component when list updates. Where should I call setState for that?
class App extends Component {
state = {
showInput: false,
addTaskInput: '',
addItem:''
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
populateTaskList = (e) => {
this.setState({addItem: e.target.value})
}
render() {
const {showInput, addTaskInput, addItem} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
onBlur={this.populateTaskList}
/> }
<TodoList
addItem={addItem}
/>
</div>
);
}
}
class TodoList extends Component {
state = {
todoList: ['a','aaa'],
todo: []
}
componentDidMount(){
const {todo, todoList} = this.state;
todoList.map((val) => {
this.state.todo.push(<div key={val}>{val}</div>)
})
this.setState({todo});
}
static getDerivedStateFromProps(nextProps, prevState){
return{
...prevState,
...nextProps
}
}
render () {
const {addItem, todoList, todo} = this.state;
return(
<div>
{todo}
</div>
)
}
}
You can keep the todoList in your app Component and pass that variable to the TodoList component:
class TodoList extends Component {
static getDerivedStateFromProps(nextProps, prevState){
return{
...prevState,
...nextProps
}
}
render () {
return(
<div>
{this.props.todoList.map((val) => <div key={val}>{val}</div>)}
</div>
)
}
}
And your app Component
class App extends Component {
state = {
showInput: false,
addTaskInput: '',
addItem:'',
todoList: [],
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
populateTaskList = (e) => {
const actualList = this.state.todoList;
actualList.push(e.target.value);
this.setState({addItem: e.target.value, todoList:actualList })
}
render() {
const {showInput, addTaskInput, addItem, todoList} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
onBlur={this.populateTaskList}
/> }
<TodoList
todoList={todoList}
/>
</div>
);
}
}
1) The only way I see is you maintain a key or index for each item only then you could update existing item. There is no way for you to know if the item is new or existing based on its value
2) To be able to update Todo's state you should give "addItem(item)" function on Todo which can be called with value that needs to be added/updated. And in that function you could update Todo's state. This function can be called from TodoList when text box loses focus or user stops typing.

How to add to state array in React

I am making a simple to-do list app in React. I have 3 states, inputText (the task the user enters), triggerAnimation(to trigger animations), and tasks (the list of tasks user has entered). However I don't know how to update the tasks state (which is an array) to push the new tasks. Here is the code.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
}
//The function triggered by button which sends the task the user has entered to the tasks array state:
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein', tasks:
this.state.inputText
})
}
render() {
//Where User enters task:
return (
<div className="App">
<main>
<div className="enterTask">
<input type="text" className="inputclass" id="textfield1"
placeholder='Enter a task.'
onChange={event => this.setState({
inputText: event.target.value })}
onKeyPress={event => {
if(event.key === 'Enter') {
this.addItem();
}
}}
/>
<br />
<br />
<button className="button"
onClick={() => this.addItem()} data-
toggle='fadein' data-target='list'>+
</button>
</div>
<!-- Where tasks will appear: -->
<div className="log">
<p className='list'>
<span class={this.state.triggerAnimation}>
{this.state.tasks}
</span>
</p>
<button className="button">-</button>
</div>
</main>
</div>
)
}
}
export default App;
However I don't know how to update the tasks state (which is an array) to push the new tasks.
Probably the cleanest way to "push to an array" in state is to use ES6 array spread. The best practice would also be to use the setState callback syntax to ensure the correct state is committed before you push the new task:
this.setState(prevState => ({
tasks: [...prevState.tasks, newTask]
}));
Seems like what you want is this..
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein',
tasks: this.state.tasks.concat(this.state.inputText)})
}
You can use .concat method to create copy of your array with new data:
addTask() {
this.setState({tasks: this.state.tasks.concat(["new value"])})
}
You also need to bind this to addTask in your constructor:
this.addTask = this.addTask.bind(this)
See my example:
https://jsfiddle.net/69z2wepo/103069/
Documentation: https://reactjs.org/docs/react-component.html#setstate
try this
import React from 'react';
class Todo extends React.Component {
constructor(props) {
super();
this.state = {
value: '',
items: []
}
}
onChange = e => this.setState({ value: e.target.value })
onEnter = e => {
if(e.charCode !== 13) return;
this.addItem();
};
onClick = e => {
this.addItem()
};
addItem = () => {
const { value } = this.state;
if(!!value.trim()) return;
this.setState(prev => ({ items: [...prev.items, value], value: '' }))
};
render() {
const { value } = this.state
return (
<div>
<div>
<input
type="text"
value={value}
name="abc"
onChange={this.onChange}
onKeyPress={this.onEnter}
/>
</div>
<button onClick={this.onClick}>Add</button>
</div>
)
}
}
FTFY better to just use comments in the code, regarding the problem(s) you want to get the tasks array then can concat the stuff to get a new array.
setState({tasks:this.state.tasks.concat([this.state.inputText])})
Wouldn't hurt to clean up the code some too... learning react myself the book "the road to learning react" has some good tips on how to set things up to be a bit more readable.
Edit actually put the right code here now...
With react, you're almost always going to have to store form field information in state (controlled components) so, how about turning todo task input field into a controlled component, like so:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
this.onInputChange = this.onInputChange.bind(this);
this.onInputKeyPress = this.onInputKeyPress.bind(this);
this.addItem = this.addItem.bind(this);
}
onInputChange(e) {
this.setState({ inputText: e.target.value });
}
onInputKeyPress(e) {
if (e.key === "Enter") {
this.addItem();
}
}
addItem() {
const itemToAdd = this.state.inputText;
const tasks = this.state.tasks;
this.setState({
inputText: "",
tasks: tasks.concat(itemToAdd);
});
}
render() {
const { inputText } = this.state;
return(
<div>
<input type="text" className="inputclass" id="textfield1" placeholder='Enter a task.'
value={inputText} onChange={this.onInputChange} onKeyPress={this.onInputKeyPress} />
<br />
<br />
<button className="button" onClick={this.addItem} data-
toggle='fadein' data-target='list'>+</button>
</div>
);
}
}
Notice how input state is controlled via component state

Categories

Resources