I have 2 examples, one with class component and one with function component doing exactly the same thing. Im using react 16.13.1. I know that if you dont persist the event you will get an error saying that the event target is null. This happens as expected in class component. In function component though, this isnt the case. What is the difference between them?
export class App extends React.Component {
constructor() {
super();
this.state = { text: '' };
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
console.log(event.target.value);
this.setState(() => ({
text: event.target.value
}));
}
render() {
return (
<div>
<span>Text: </span>
<input onChange={this.handleChange} value={this.state.text} />
</div>
);
}
}
const App = () => {
const [state, setState] = useState({ text: "" });
const handleChange = (event) => {
console.log(event.target.value);
setState({
text: event.target.value,
});
};
return (
<div>
<span>Text: </span>
<input onChange={handleChange} value={state.text} />
</div>
);
};
This all comes down to the timing for when this.setState() and setState() is called. In your class component, the additional arrow function declaration inside this.setState causes an additional delay that makes the call take longer than the useState hook in the functional component. If you wrap the setState() call in your functional component with a setTimeout of 100, you get an error as expected. Also, if you modify the setState call to this.setState({ text: event.target.value }) in your class-based component, you no longer get an error. Finally figured that out with some inspiration from the official React docs.
Related
In class component when we want to pass the object to the value of context provider using react context, we have a way to avoid re-rendering issue. Below are the codes
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
contextState: {
count: 0,
increment: this.increment
}
};
}
increment = () => {
this.setState({
contextState: {
...this.state.contextState,
count: this.state.contextState.count + 1
}
});
};
onChange = e => {
const { value, name } = e.target;
this.setState({ [name]: value });
};
render() {
return (
<CountContext.Provider value={this.state.contextState}>
<div style={styles}>
<input name="text" value={this.state.text} onChange={this.onChange} />
<div>Count: {this.state.contextState.count}</div>
<Container1 />
<Container2 />
</div>
</CountContext.Provider>
);
}
}
We put this.state.contextState to value of CountContext.Provider. So when user types anything in input element and will not cause <Container1 /> and <Container2 /> re-rendered. Here is the code sandbox: https://codesandbox.io/s/qqx1jqk8mj?file=/src/index.js:260-1105
I am tring to convert it into hooks. Here is the code sandbox https://codesandbox.io/s/affectionate-gauss-duk64?file=/src/index.js but the counter is not working properly. May I please know which part is wrong? thanks
In your hook component, you just need to use the functional setState approach.
setContextState(prevState=>newState)
In your code: https://codesandbox.io/s/admiring-shtern-g6oll?file=/src/index.js
const [contextState, setContextState] = useState({
count: 0,
increment: () => {
setContextState(prev=>({
...prev,
count: prev.count + 1
}));
}
});
The reason you need to do this is because the state value will never update because of the closure around it. contextState.count will always remain at 0 because it was the value when the state was originally set (0), and it won't change.
class Usurvey extends Component {
constructor(props) {
super(props);
this.nameRef = React.createRef();}
state={
uid:uuid.v1(),
studentName:'',
isSubmitted:false
}
nameSubmit=(e)=>{
let studName=this.nameRef.current.value;
console.log(studName);
this.setState({studentName:studName});
console.log(this.state);
}
render() {
let studentName;
if(this.state.studentName===''&&this.state.isSubmitted===false){
studentName=<div>
<h1>hey student pls let us know your name </h1>
<form onSubmit={this.nameSubmit}>
<input type='text' placeholder="name pls " ref={this.nameRef} className="namy" />
</form></div>}
return (
<div>
{studentName}
-------------------------------
{questions}
</div>
);
}
}
export default Usurvey;
see in this my question is that in namesubmit function when the form is submitted it should update the value of the state.studentName to the input given by the user and then log it on the console
and it is not working in above code but if i change the namsubmit function then it is working why ? pls explain
this is the new code
nameSubmit=(e)=>{
let studName=this.nameRef.current.value;
console.log(studName);
this.setState({studentName:studName},()=>{
console.log(this.state);
});
}
setState is async call. By doing like so-
this.setState({studentName:studName});
console.log(this.state)
State indeed gonna change in next render, but you are logging it before it happen.
At your new code
nameSubmit=(e)=>{
let studName=this.nameRef.current.value;
console.log(studName);
this.setState({studentName:studName},()=>{
console.log(this.state);
});
}
You set the log function as a callback, what gonna be executed only after setState finished it job
setState works asynchronously so the state of your component is not guaranteed to be updated right after the setState call. In your second snippet of code you provide the second argument as a callback function which is called after the state update so you can be sure the state is up-to-date when you log it into the console
setState is asynchronous, meaning the state might not be yet set by the time the next line is executed.
By using
this.setState({studentName:studName},()=>{
console.log(this.state);
});
You are defining a callback function and passing it to setState
()=> {
console.log(this.state);
});
setState will then, change the state, and only once it's done (which could be after nameSubmit is finished, it will execute your callback function, guarantying that the state is up to date on it.
I don't know what exactly is your coding doing but there is no questions variable defined in your code. So here is the full code:
import React from "react";
import { v1 } from "uuid";
export default class App extends React.Component {
constructor(props) {
super(props);
this.nameRef = React.createRef();
}
state = {
uid: v1(),
studentName: "",
isSubmitted: false
};
nameSubmit = e => {
let studName = this.nameRef.current.value;
this.setState({ studentName: studName }, () => {
console.log(this.state);
});
};
render() {
let studentName;
if (this.state.studentName === "" && this.state.isSubmitted === false) {
return (
<div>
<h1>hey student pls let us know your name </h1>
<form onSubmit={this.nameSubmit}>
<input type="text"
placeholder="name pls "
ref={this.nameRef}
className="namy"
/>
<button type="submit">Submit</button>
</form>
</div>
);
} else {
return <div>{this.state.studentName}</div>;
}
}
}
I'm very new to react and I got two problems:
I want to console log the input and display the mapped data after clicking the submit button once. But I get console logged the input and the mapped data after clicking the button twice.
I wanna clear the mapped list (data from previous input) and display new list items depending on the input. But the new list items are only added to the end of the previous list (only the last list item from the previous list got overwritten by the first list item of the new list).
So this is the code from my app component:
import React, { Component, Fragment } from 'react';
import './App.css';
import Display from './/Display';
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
passedValue: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
handleSubmit(event) {
this.setState({ passedValue: this.state.value });
console.log(this.state.passedValue);
event.preventDefault();
}
render() {
return (
<div>
<form className="inputContainer" onSubmit={this.handleSubmit}>
<input type="text" name="company_name" onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
<Display listDataFromParent={this.state.passedValue} />
</div>
);
}
}
export default App;
And this is my display component:
import React, { Component } from 'react'
import "./Display.css";
export default class Display extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: []
};
}
componentWillReceiveProps() {
fetch("http://localhost:5000/company?company_name=" + this.props.listDataFromParent)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
data: result
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, data } = this.state;
// if (error) {
// return <div>Error: {error.message}</div>;
// } else if (!isLoaded) {
// return <div>Loading...</div>;
// } else {
return (
<div className="display">
<h1>Kreditnehmer</h1>
<ul>
{this.props.listDataFromParent}
{data.map(item => (
<li key={item.c.company_id}>
Relation type: {item.r.relation_group}
Last name: {item.p.last_name}
</li>
))}
</ul>
</div>
);
}
}
Can anyone help?
1) setState is async method in react means it will take some time to update the component state. You can get your console log by using callback function of setState like
this.setstate({ value: e.target.value }, () => { console.log(this.state.value) });
2) in display component, your using componentWillReciveProps life cycle and inside that your using this.props.listdatafromparent which is pointing previous props. Rather than using this.props I would suggest consider props param of life cycle, means it should be like
componentWillReciveProps(props) {
// your code
Console.log(props.listdatafromparent);
}
The handleSubmit method is wrong... the console log is executed before the state is changed. You need to put it inside a callback function as a second parameter of setState.
this.setState({ passedValue: this.state.value }, () => {
console.log(this.state.passedValue);
});
Answers are:
1) Callback function should be used on setState, in order to do console.log after state is really updated.
In your case you call setState and setState is async function, which means that console.log won't wait until state is really updated.
Your code should be:
handleSubmit(event) {
this.setState({ passedValue: this.state.value },
() => console.log(this.state.passedValue));
event.preventDefault();
}
2) I would move data fetching out of componentWillReceiveProps(), since this lifecycle method will be deprecated from version 17 and it is fired on every render(). Try replacing with componentDidMount() or componentDidUpdate(). Maybe just this small change will solve your problem. If not pls post results and I will take a look again.
I have an app with one child component that I would like to re-render when setState updates the bookInput in the parent's state. I am using axios to request info from google's book api. For some reason, even though the state is updating, the child is not re-rendering. Please help if you can! Thank you!
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = {
bookInput: 'ender',
bookSubmitted: 'initial'
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleSubmitEmpty = this.handleSubmitEmpty.bind(this);
}
handleChange(e) {
this.setState({bookInput: e.target.value});
console.log(this.state.bookInput);
//this.setState({bookSubmitted: false});
}
handleSubmit(e) {
e.preventDefault();
//this.setState({bookSubmitted: true})
const name = this.state.bookInput;
this.setState({bookInput: name});
console.log(this.state);
this.setState({bookSubmitted: 'userSub'});
}
handleSubmitEmpty(e) {
alert('please enter an item to search for');
e.preventDefault();
}
render() {
return (
<div className="App">
<header className = "App-header">
<h1>Book Search App</h1>
</header>
<form className = "form-style" onSubmit = {this.state.bookInput ? this.handleSubmit: this.handleSubmitEmpty}>
<label>
<input type="text" className = "input-style"
value = {this.state.bookInput} onChange = {this.handleChange}>
</input>
</label>
<button type="submit">search books</button>
</form>
{/* <Book bookInput = {this.state.bookInput}/> */}
{/*this.state.bookSubmitted && <Book bookInput = {this.state.bookInput}/>*/}
{
(this.state.bookSubmitted === 'initial' || this.state.bookSubmitted === 'userSub') &&
<Book bookInput = {this.state.bookInput}/>
}
</div>
);
}
}
export default App;
class Book extends Component {
constructor(props) {
super(props);
this.state = {
//bookInput2: "ender",
bookTitles: [],
bookExample: '',
isLoading: false
}
this.bookClick = this.bookClick.bind(this);
}
bookClick(book) {
console.log(book);
console.log(book.volumeInfo.infoLink);
const bookURL = book.volumeInfo.infoLink;
window.open(bookURL);
}
componentDidMount() {
//this.setState({ isLoading: true });
this.setState({isLoading: true});
axios.get(`https://www.googleapis.com/books/v1/volumes?q=${this.props.bookInput}`)
.then((response) => {
const bookExample1 = response.data.items;
console.log(bookExample1);
this.setState({bookTitles: bookExample1, isLoading: false});
})
.catch((error) => {
console.error('ERROR!', error);
this.setState({isLoading: false});
});
}
render() {
return (
<div>
{ this.state.bookTitles ? (
<div>
<h2>book list</h2>
{<ul className = 'list-style'>
{this.state.isLoading &&
(<div>
loading book list
</div>)
}
{this.state.bookTitles.map(book => (
<li key={book.id}>
<span className = 'book-details book-title' onClick = {() => this.bookClick(book)}> {book.volumeInfo.title}</span>
<br/>
{book.volumeInfo.imageLinks &&
<img src = {book.volumeInfo.imageLinks.thumbnail}/>
}
{ book.volumeInfo.description &&
<span className = 'book-details'>{book.volumeInfo.description}</span>
}
<br/>
<span className = 'book-details'>Categories {book.volumeInfo.categories}</span>
</li>
))}
</ul>}
</div>) :
(<p>sorry, that search did not return anything</p>)}
</div>
);
}
}
May be you are looking for something similar to this?
https://stackblitz.com/edit/react-snoqkt?file=index.js
The above code can be simplified more and organized but it gives you some idea.
Main changes in the code.
Changed Api call from componentDidMount lifecycle event to a new method named getInitialdata which is called in handleSubmit.
getInitialdata(name){
axios.get(`https://www.googleapis.com/books/v1/volumes?q=${name}`)
.then((response) => {
const bookExample1 = response.data.items;
console.log(bookExample1);
this.setState({bookTitles: bookExample1, isLoading: false, bookSubmitted: 'userSub'});
})
.catch((error) => {
console.error('ERROR!', error);
this.setState({isLoading: false, bookSubmitted: 'userSub'});
});
}
Changed the way how Child component is used.
<Book bookTitles={this.state.bookTitles} isLoading={this.state.isLoading}/>
Issue with your code is you are making an API call in your component's didMount method. This lifecycle event will be invoked only when the component is mounted. Not when it is updated.
When you enter some input in your textbox and click on "Search books", componentDidMount event doesnt fire. And this is the reason why API calls are not happening from the second time.
More on the lifecycle events at https://reactjs.org/docs/react-component.html#componentdidmount
I've taken your code and extrapolated it into this sandbox. Just as you said, your parent component state is updating as it should, but the problem is that the child component doesn't change its state.
A state change will always trigger a re-render in React. The only problem is, your child component is managing it's own state, which isn't directly changing. Instead, it's just receiving new props again and again, but not doing anything with them.
If you look at your code for the <Book /> component, you only modify its state on componentDidMount, which only happens once. If you'd like to programmatically make it update, you can do one of two things.
Remove state from the child component, and make it rely entirely on props, so that it stays in sync with the parent
Use the componentDidUpdate lifecycle method (docs) to choose when to change the state of the child (which will trigger the re-render)
I have TimePickerInput component like this
class TimePickerInput extends React.Component {
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool
};
static defaultProps = {
onChange: function() {}
};
componentDidMount() {
this._$input
.timepicker({
autoclose: true,
minuteStep: 5,
showSeconds: false,
showMeridian: false
})
.on("changeTime.timepicker", e => {
this.props.onChange(e.time.value);
});
this._$input.timepicker("setTime", this.props.value);
}
componentWillReceiveProps(nextProps) {
this._$input.timepicker("setTime", nextProps.value);
}
render() {
const { disabled } = this.props;
return (
<Wrapper>
<IconWrapper>
<Icon className="fa fa-clock-o" />
</IconWrapper>
<Input
innerRef={ref => (this._$input = $(ref))}
type="text"
size="16"
className="form-control"
readOnly
disabled={disabled}
/>
</Wrapper>
);
}
}
export default TimePickerInput;
When I using it in my another component like this
<TimePickerInput value={notificationConfig.timeToSendAuditReminder} onChange={e => this.onChangeTimeField(e, 'timeToSendAuditReminder')}/>
and have onChangeTimeField like this
onChangeTimeField = (value, fieldName) => {
this.props.dispatch(notificationConfigActions.requestUpdateTimeField(value, fieldName))
}
I always get an error like this
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I cannot find solution how to fix this error. I think it happened when there are a lot of onChange event call between on("changeTime.timepicker") and when requestUpdateTimeField(value, fieldName) done
This might be because you're using componentWillReceivePropsuncondtionally. See this see this. You should use one of these instead of componentWillReceivePropsas it is considered legacy now.