*edit to provide solution in comments
I have an app that renders 2 components, a SearchBar form and a Table of data. After the app mounts, an api call is made and setState is called, which triggers a render of the Table. It works fine.
The problem comes from the SearchBar component. On submission, the functional argument handleSubmit is called to make an api request and set the new state. SetState should trigger a render but it doesn't. The state is verified and accurate but there is no render.
Here is my code.
App.js
class App extends Component {
constructor(props) {
console.log('app constructor')
super(props)
this.state = {
items: [],
}
}
render() {
console.log('app render')
return (
<div>
<SearchBar onSubmit={this.handleSubmit} />
<Table data={this.state.items} />
</div>
)
}
componentDidMount() {
console.log('app mounted')
fetch('/api/items/?search=initial search')
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
console.log('state post mount ' + this.state.items.length)
})
}
handleSubmit(e) {
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
console.log('state post submit ' + this.state.items[0].name)
})
}
}
SearchBar.js
export default class SearchBar extends Component {
constructor(props) {
console.log('search bar constructor')
super(props)
this.onChange = this.handleChange.bind(this)
this.onSubmit = this.props.onSubmit.bind(this)
this.state = {
value: ''
}
}
handleChange(e) {
console.log('search bar changed ' + e.target.value)
this.setState({
searchBarValue: e.target.value
})
}
render() {
return (
<form className='form' id='searchForm' onSubmit={this.onSubmit}>
<input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
</form>
)
}
}
Table.js
export default class Table extends Component {
render() {
if (this.props.data.length === 0) {
return (
<p>Nothing to show</p>
)
} else {
return (
<div className="column">
<h2 className="subtitle">
Showing <strong>{this.props.data.length} items</strong>
</h2>
<table className="table is-striped">
<thead>
<tr>
{Object.entries(this.props.data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{this.props.data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</table>
</div>
)
}
}
}
Please set this in a variable, when function initiate:-
handleSubmit(e) {
let formthis=this;
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
formthis.setState({
items: data
})
console.log('state post submit ' + formthis.state.items[0].name)
})
}
AS I said in the comment, Remove this line this.onSubmit = this.props.onSubmit.bind(this) from the SearchBar component and replace this one
<form className='form' id='searchForm' onSubmit={this.onSubmit}>
with
<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
The problem is when you call bind the onSubmit from the props with the this as you did it is using the context of the SearchBar and not the parent so it sets the response to the state of the Search bar and not the App component which you want that way your items state of the parent component never changes an as such you don't get a re-render
Here is the relevant code for my solution. As harisu suggested, I changed the declaration of the form component. I also added a bind statement in the constructor of the parent.
App.js
class App extends Component {
constructor(props) {
console.log('app constructor')
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
items: [],
}
}
handleSubmit(e) {
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
})
console.log('state post submit ' + this.state.items[0].name)
}
}
SearchBar.js
export default class SearchBar extends Component {
render() {
return (
<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
<input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
</form>
)
}
}
Related
Whenever I trigger readItem() and handleChange() afterwards, browser throws error "this.setState is not a function". HandleChange() works well otherwise. Could someone advise what is causing the error and how to fix it? I also expecting the readtItem() function to update the state, which doesn't happen. Any help will be appreciated.
Thanks
import React from 'react';
import ListItems from '../src/components/ListItems/ListItems.component';
import Button from '../src/components/button/button.component';
import axios from 'axios';
// import axios from 'axios';
// import Home from './components/home/home.component';
import './App.css';
class App extends React.Component {
constructor(props){
super(props);
this.state = {
items:[],
readItem: {
name:'',
email:'',
phone:'',
myId:''
},
currentItem:{
name:'',
email:'',
phone:''
}
}
}
addItem = e => {
e.preventDefault();
const { name,email,phone } = this.state.currentItem;
const myId = Math.random().toString();
const items = [...this.state.items, {name, email, phone, myId: myId}];
this.setState({
items: items,
currentItem:{
name:'',
email:'',
phone:'',
myId:''
}
})
const config = {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials':true
}
};
axios.post('/records', {name , email, phone, myId: myId}, config)
.then(res => {
console.log(res);
console.log(res.data);
})
}
handleChange = event => {
const { name, value } = event.target;
this.setState({
currentItem: {
...this.state.currentItem, [name]: value
}
});
}
deleteItem = myId => {
const filteredItems= this.state.items.filter( item => item.myId!==myId);
this.setState({
items: filteredItems
})
axios.delete('/records/' + myId, {data: {id: myId}})
.then(res => console.log(res))
}
componentDidMount(){
axios.get('/records')
.then(res => {
this.setState({items:res.data});
})
}
readItem = item => {
this.setState = {
readItem: item
}
}
render(){
return (
<div className="App">
<div className='container'>
<div className='form-container'>
<form className='form' onSubmit={this.addItem}>
<input type='text' name='name' placeholder='Name' autoComplete='off' onChange={this.handleChange} value={this.state.currentItem.name} />
<input type='text' name='email' placeholder='Email Address' autoComplete='off' onChange={this.handleChange} value={this.state.currentItem.email} />
<input type='text' name='phone' placeholder='Mobile Number' autoComplete='off' onChange={this.handleChange} value={this.state.currentItem.phone} />
<Button className='submit-button' type='submit' name='Submit' color='white'/>
</form>
</div>
<div className='table-container'>
{
this.state.items.length > 0 ?
(<table className='table'>
<thead className='table-header'>
<tr>
<th>Name</th>
<th>Email Address</th>
<th>Mobile Number</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<ListItems items={this.state.items} deleteItem={this.deleteItem} readItem={this.readItem}/>
</tbody>
</table> )
:null
}
</div>
</div>
{
this.state.readItem ?
(<div className='popup'>
<p>Name: </p>
<p>Email Address: </p>
<p>Mobile Number: </p>
</div>)
: null
}
</div>
);
}
}
export default App;
you can't update state like this.setState = {readItem: item}
setState is a function and should use like this.setState({items: filteredItems})
try to make all your methods arrow functions like handleChange=()=>{} they do not have this in normal function
As you are using this.setState, it sounds like you are using a Component class. When a callback is called on a child element (such as onChange etc.) The context (this) is set to the child element itself. For the correct "this" context to be preserved, you need to bind the callback functions.
In your class' constructor, add the following to bind your methods:
this.handleChange = this.handleChange.bind(this);
See here for more information on handling events
In the future please upload snippets of the code in question.
Make sure this is bound within your callbacks for ex. handleChange by adding the binding code inside your constructor.
constructor(props) {
super(props);
this.state = {
items:[],
readItem: {
name:'',
email:'',
phone:'',
myId:''
},
currentItem:{
name:'',
email:'',
phone:''
}
}
// Add below line for all callbacks
this.handleChange = this.handleChange.bind(this)
}
You can also use arrow function in the callback if you don't want to use the bind code inside constructor. Here is a class component CodeSandbox for both of the use cases. The example is adapted from official doc.
If you are working on a new codebase or new to React I would highly encourage you to use functional components in place of class components. In my opinion, functional components and useState APIs are much simpler and less verbose than class components. Here is a functional component CodeSandbox for the above example.
What I want is that, when I click on search button, then a loader/spinner should appear on screen until the data is fetched, when the data is fetched it should disappear.
Container.jsx
import React from 'react';
import './container.css'
import Weather from './weather';
var Loader = require('react-loader');
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
location: "",
weather: [],
loaded:false
};
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
componentDidMount() {
this.setState.loaded=false;
}
continue = (e) => {
this.setState({loaded:true});
const { location } = this.state;
const rawurl = 'http://api.weatherstack.com/current?access_key=d8fefab56305f5a343b0eab4f837fec1&query=' + location;
const url = rawurl;
e.preventDefault();
if (location.length < 1) {
return alert('Enter the details');
}
else {
fetch(url)
.then(response => response.json())
.then(data =>{
this.setState({weather:[data],loaded:false});
})
.catch(err => console.log("error ",err))
}
};
render() {
console.log(this.state.weather);
const weather =
this.state.weather.length> 0 ?
this.state.weather.map(item => (<Weather location={item.location.name} temperature={item.current.temperature} weather={item.current.weather_descriptions[0]} windSpeed={item.current.wind_speed} windDegree={item.current.wind_degree} windDir={item.current.wind_dir} humidity={item.current.humidity} visibility={item.current.visibility} />
))
:<span></span>
return (
<div id="container">
<div class="searchicon">
<input type="search" placeholder="Enter City !!" type="text" name="location" value={this.state.location} onChange={this.handleChange}></input>
<label class="icon">
<button onClick={this.continue} id="btn"><span class="fa fa-search"></span></button>
</label>
</div>
<div>
<Loader loaded={this.state.loaded}>
{weather}
</Loader>
</div>
</div>
);
}
}
export default Container;
What I am using here is react-loader
But right now,its not happening in the way I want, sometime before clicking the serach button it appears and when data is fetched it stops, i want to start it when the api req is made after click on search button and to stop when data is fetched.
first of all you should in the setState after fetching the data to make
this.setState({weather:[data],loaded:true});
second there's another way to do it you can separate the code in the return function like
{ !this.state.loaded ? <Loader loaded={false} options={options} className="spinner" />:{weather}}
as per the Doc in npm you can check it react-loader
Problem
When a user checks/un-checks a checkbox, a post request is made via the onChange event. In order to avoid hammering the API, I am using _.debounce. However, I want to immediately update state when the onChange event is fired.
I understand that my current code wont't allow for that, since I am updating state in updateTodoItem(), which is run in the debounced handleChange() function.
Question.
How do I continue to debounce the post request, while immediacy invoking this.setState()?
Simplified Code
...
import _ from "lodash";
import axios from "axios";
import setAxiosHeaders from "./AxiosHeaders";
class TodoItem extends React.Component {
constructor(props) {
super(props);
this.state = {
complete: this.props.todoItem.complete
};
this.handleChange = this.handleChange.bind(this);
this.updateTodoItem = this.updateTodoItem.bind(this);
this.inputRef = React.createRef();
this.completedRef = React.createRef();
this.path = `/api/v1/todo_items/${this.props.todoItem.id}`;
}
handleChange() {
this.updateTodoItem();
}
updateTodoItem() {
this.setState({
complete: this.completedRef.current.checked
});
setAxiosHeaders();
axios
.put(this.path, {
todo_item: {
title: this.inputRef.current.value,
complete: this.completedRef.current.checked
}
})
.then(response => {})
.catch(error => {
console.log(error);
});
}
render() {
const { todoItem } = this.props;
return (
<tr className={`${this.state.complete ? "table-light" : ""}`}>
<td>
...
</td>
<td className="text-right">
<div className="form-check form-check-inline">
<input
type="boolean"
defaultChecked={this.state.complete}
type="checkbox"
onChange={_.debounce(this.handleChange, 1000)}
ref={this.completedRef}
className="form-check-input"
id={`complete-${todoItem.id}`}
/>
<label
className="form-check-label"
htmlFor={`complete-${todoItem.id}`}
>
Complete?
</label>
</div>
</td>
</tr>
);
}
}
export default TodoItem;
I just needed to call _.debounce() on updateTodoItem.
handleChange() {
this.setState({
complete: this.completedRef.current.checked
});
this.updateTodoItem();
}
updateTodoItem = _.debounce(() => {
setAxiosHeaders();
axios
.put(this.path, {
todo_item: {
title: this.inputRef.current.value,
complete: this.completedRef.current.checked
}
})
.then(response => {})
.catch(error => {
console.log(error);
});
}, 1000);
<input
type="boolean"
defaultChecked={this.state.complete}
type="checkbox"
onChange={this.handleChange}
ref={this.completedRef}
className="form-check-input"
id={`complete-${todoItem.id}`}
/>;
So I would like to call fetch from a function (submitURL) in App.js. If I create "componentDidMount()" in App.js and call fetch there, it works, but not from submitURL. I believe this is because submitURL is called via prop drilling. How would I call fetch from submitURL?
App.js
class App extends Component {
state = {
channelURL: '',
videos: []
}
submitURL = (value) => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
this.setState({
channelURL: value
});
}
render() {
console.log(this.state)
return (
<div className="App">
<h1> Title </h1>
<Channel submitURL={this.submitURL} url={this.state.channelURL}/>
<Videos videos={this.state.videos}/>
</div>
);
}
}
export default App;
Channel.js
class Channel extends Component {
state = {
value: this.props.url
}
handleChange = (e) => {
this.setState({
value: e.target.value
});
}
render() {
return (
<div>
<h1> Enter Channel URL </h1>
<form onSubmit={this.props.submitURL.bind(this, this.state.value)}>
URL: <input type="text" name="url" value={this.state.value} onChange={this.handleChange}/>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default Channel;
submitURL = (value) => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => this.setState({
channelURL: json
}))
}
I want to know if it is possible to send a form which is not in react component by fetch in react component.
<form action="json.bc" class="form_search" method="post">
<input type="hidden" name="Firstname" value="">
<input type="hidden" name="Familyname" value="">
<!-- ... -->
</form>
<div id="Result"></div>
The form class="form_search"is outside of the <div id="Result"></div>. I want to submit the form in react component.
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
data: []
};
}
componentDidMount() {
fetch("/json.bc", {
method: "POST"
})
.then(response => response.text())
.then(text => {
var Maindata = JSON.parse(text.replace(/\'/g, '"'));
this.setState(
state => ({
...state,
data: Maindata
}),
() => {}
);
})
.catch(error => console.error(error));
}
render() {
const { data } = this.state;
const renderInfo = data.map((item, i) => {
return <span>{item.name}</span>;
});
return <div class="loading_content">{renderInfo}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("Result"));
Actually I want to have another fetch() request in component to submit this form also I can not add the form in component.
Add on Form tag onSubmit event <Form onSubmit={this.handleSubmit}>
you will need a button also inside of Form which will submit the From.
<button>Submit</button>