I am trying to do on change on datepicker, but the state doesn't update. I am using a state from props. So the date already exists. It shows expired dates. When I am doing the on change, it get stuck at the current date.
Something I am missing in the onChange handler?
constructor(props) {
super(props);
const {export} = this.props;
this.state = {showCalender: false};
this.date = export.expires;
this.handleChange = this.handleChange.bind(this);
}
static getDerivedStateFromProps(props, state) {
if (props.export.expires !== state.expires) {
return {
expires: props.export.expires
};
}
return null;
}
handleChange(date) {
this.setState({
expires: date
}, console.log(this.state.expires));
this.handleClick();
}
handleClick() {
this.setState({showCalender: !this.state.showCalender});
}
handleClear() {
this.setState({expires: ''});
}
render() {
const {expires, showCalender} = this.state;
const expiresDate = format(expires, 'MM/dd/yyyy');
return (
<div>
<FormGroup>
<FormControl
id="date"
value={expiresDate}
onChange={this.handleChange}
onClick={() => this.handleClick()}
title="set date"
aria-label="set date"
/>
<Button className="close-btn" onClick={() => this.handleClear()}>Clear</Button>
</FormGroup>
{ showCalender && (
<FormGroup>
<DatePicker
selected={expires}
onChange={this.handleChange}
inline
/>
</FormGroup>
)}
</div>
);
}
When you update the state you are triggering a re-render, but just before the next render, react is calling the getDerivedStateFromProps which there you are checking to see if the values are different:
static getDerivedStateFromProps(props, state) {
if (props.export.expires !== state.expires) {
return {
expires: props.export.expires
};
}
return null;
}
Which they are, as you just updated the state but the props stayed the same.
And then you update the state again but now you set it back to whatever the value in props is.
From the DOCS:
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates.
I'm not sure why you are trying to sync props and state, but usually this indicates a bad design of your app.
As mentioned in the comments, do not use a variable named export as its a reserved word since ES2015.
You might get an error of:
Unexpected keyword 'export'
Related
I have a parent component that renders a child component and passes it's initial state to the child component. I need a few clarifications- My gut feeling is to handle the event change in the Child component, is this correct? Upon submission, how do I pass the updated props back to parent? My gut is also telling that once the props is passed back to the parent, I can use componentDidUpdate() to set the state to be used elsewhere. If so, how?
class Parent extends React.Component {
constructor() {
super();
this.state = {
arrival: "arrival",
departure: "destination"
};
}
componentDidUpdate(){
// how to update state?
}
render() {
const { arrival, departure } = this.state;
return <Child arrival={arrival} departure={departure} />;
}
}
class Child extends React.Component{
constructor(){
this.handleSubmission = this.handleSubmission.bind(this);
}
handleSubmission(e){
const target = e.target;
const name = target.name;
const value = target.value;
// not sure how to handle props from here
}
render(){
let { arrival, departure } = this.props;
return(
<form onSubmit = {this.handleSubmission} >
<div class="form-group">
<label for="departure">Departure</label>
<input type="" class="form-control" name="departure" aria-describedby="emailHelp" placeholder="Enter Departing Station"/>
</div>
<div class="form-group">
<label for="arrival">Arrival</label>
<input type="password" class="form-control" name="arrival" id="inputArrival" placeholder="Enter Arriving Station"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
)
}
}
export default Child
#hello_there
Please disregard the previous answer, as it requires a lot more than just changing a few props.
I've forked the Sandbox and rewrote it here (so you can follow along).
I've outlined the steps to make the change to propagate to the parent below.
Capture the states of <input />
First step is to capture the state of form fields.
There are two ways to handle form fields
Controlled Components
Uncontrolled Components - discouraged
I am going to use the former (controlled) to capture the form field states by adding a state to Form component.
And you need to set the value={...} of each state and update each state from onChange event (using handleInputChange added below) for each form field.
I've added 👇 where changes were made
import React, { Component } from "react";
class Form extends Component {
// .. 👇
state = {
departure: "",
arrival: ""
};
//... rest removed for brevity
// .. 👇 is used to update each form field state.
handleInputChange = e => {
e.preventDefault();
const { name, value } = e.target;
this.setState({ [name]: value });
};
render() {
const { departure, arrival } = this.state;
return (
<form onSubmit={this.handleSubmission}>
<div className="form-group">
<label> Departure</label>
<input
className="form-control"
name="departure"
placeholder="Enter Departing Station"
// ... 👇 ...👇
value={departure} onChange={this.handleInputChange}
/>
</div>
<div className="form-group">
<label> Arrival</label>
<input
className="form-control"
name="arrival"
id="inputArrival"
placeholder="Enter Arriving Station"
// ... 👇 ...👇
value={arrival} onChange={this.handleInputChange}
/>
</div>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
);
}
}
Update App's state change event handler
Now we have states handy, we need to update the App's updateState to accept a whole new state, so we don't make multiple method calls (this.props.updateParentState) and it'd let us pass a new reference so that React would know that the state has been changed in the App component.
class App extends Component {
constructor() {
super();
this.state = {
arrival: "arrival",
departure: "departure"
};
}
// From this 👇
// updateState = (name, value) => {
// this.setState({
// [name]: value
// });
// };
// to this 👇
updateState = newState => this.setState(newState);
componentDidUpdate(prevProps, prevState) {
const { arrival, departure } = this.state;
console.log(`Arrival: ${arrival}, departure: ${departure}`);
}
render() {
const { arrival, departure } = this.state;
return (
<Fragment>
<Form
arrival={arrival}
departure={departure}
// 👇 stays the same
updateParentState={this.updateState}
/>
</Fragment>
);
}
}
Update Child's submission event handler
Now the App.updateState accepts a state object, which can be used to update App.state, let's change Child.handSubmission.
handleSubmission = e => {
e.preventDefault();
// this.props.updateParentState(name, value);
this.props.updateParentState(this.state);
};
You can see that this.props.updateParentState(name, value) has been replaced with this.props.updateParentState(this.state), which would let us update App.state at once.
Now you should be able to see the change in the App.componentDidUpdate.
OLD ANSWER - Disregard this
Changing the state in the child doesn't cause the re-render in the parent (so componentDidUpdate is probably not triggered by change in child component). So what you can do is to pass the event handler down to the child, which the child can notify the Parent component that something has changed.
I've explained the flow of how you can update the parent's states.
First you need to create a handler, with which you can update the Parent's state with.
updateState = (name, value) => this.setState({ [name]: value });
Here, I am using [name], which is a dynamic property. So it should match up with the Parent's state name. In this case either arrival or departure.
Then you need to pass that event handler to the Child (you can name the prop name to whatever (In the code below, I used updateParentState but it can be updateWhatever so long as you pass it the updateState correctly).
<Child arrival={arrival} departure={departure} updateParentState={updateState} />
Here is the complete code that will update the parent state.
Changes are indicated with "👇" emoji below.
class Parent extends React.Component {
constructor() {
super();
this.state = {
arrival: "arrival",
departure: "destination"
};
}
// This is what you pass to the child as a `prop` to call.
updateState = (name, value) => this.setState({ [name]: value });
render() {
const { arrival, departure } = this.state;
// pass the event handler to the child component ... 👇 ...
return <Child arrival={arrival} departure={departure} updateParentState={updateState} />;
}
}
class Child extends React.Component{
constructor(){
this.handleSubmission = this.handleSubmission.bind(this);
}
handleSubmission(e){
const target = e.target;
const name = target.name;
const value = target.value;
// 👇 Update the "Parent"'s state.
// `name` & `value` will be the supplied to `Parent.updateState`
this.props.updateParentState(name, value)
}
render(){
// ... removed for brevity
}
}
If the statement management gets too hard to manage with prop-drilling, then you can reach out for Context API (after familiarizing with it, you can check out How to use React Context effectively, which uses Hooks).
Or you can use Redux.
Just pass to your children a reference on how to update the state:
class Parent extends React.Component{
state = {departure : ''}
setDeparture = departure => this.setState({ departure })
render(){ <Child setDeparture={this.setDeparture} /> }
}
const Child = ({setDeparture}) => <button onClick={() => setDeparture('foo')}>Click</button>
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)
after getting data from API I want to show them into inputs,edit and update it in DB. I thought that beside the redux state, I should use also local state , but some people here say that is not good practise .So how I can handle my onChange methods and how pass updated data into axios.put method???
class ArticleEdit extends Component {
articleID = this.props.match.params.articleID;
state={
title:'',
text:'',
imgs:[]
}
onChange =(e)=>{}
componentDidMount(){
this.props.getArticleDetails(this.articleID);//get data from API
}
render() {
return (
<Fragment>
{this.props.article===undefined?(<Spin/>):
(
<div >
<div >
<Form onSubmit={this.handleSubmit}>
<Input name='title'
value='this.props.article.title'
onChange={this.onChange}/>
<Textarea
name='text'
value={this.props.article.title}
onChange={this.onChange}/>
<Button htmlType='submit'>Update</Button>
</Form>
</div>
</div>
)}
</Fragment>
)
}
}
const mapStateToProps = state =>({
article: state.articleReducer.articles[0],
})
export default connect(mapStateToProps,{getArticleDetails})
(ArticleEdit);
So how I can handle my onChange methods and how pass updated data into
axios.put method???
Well if that's literally what you want to do, then you can do it like this:
onChange = e => {
try {
const results = await axios.put(someurl, e.target.value)
console.log('results', results)
} catch(e) {
console.log('err', e)
}
}
This will call axios.put after every keystroke - however, I doubt that is what you want.
I've found the solution. I used the static method getDerivedStateFromProps,which calls everytime when props of your component has been changed.
static getDerivedStateFromProps(nextProps, prevState){
if(prevState.title===null && nextProps.article!==undefined){
return{
title:nextProps.article.title,
text:nextProps.article.text
}
}
return null;
}
after that is easy to work with onChange method ,which just call this.setState().
I have a Parent component:
import React, { Component } from "react";
import { Button } from "./Button";
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
numbers: [],
disabled: false
};
this.setNum = this.setNum.bind(this);
}
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(prevState => ({
numbers: [...prevState.numbers, num]
}));
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums });
console.log(this.state.numbers);
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
render() {
return (
<div className="board-container">
<div className="board">
<div className="row">
<Button
id="1"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="2"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="3"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="4"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
</div>
</div>
</div>
);
}
}
... and a Child component:
import React, { Component } from "react";
export class Button extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
isChecked: !this.state.isChecked
});
var num = e.target.value;
this.props.onChange(num);
}
render() {
const { isChecked } = this.state;
if (isChecked === true) {
var bgColor = "#f2355b";
} else {
bgColor = "#f7f7f7";
}
let disabled = this.props.disabled;
if (this.props.numbers.includes(this.props.id)) {
disabled = false;
}
return (
<div className="number-container" id="checkboxes">
<label
className={!isChecked && disabled === false ? "num" : "checked-num"}
style={{ backgroundColor: bgColor }}
>
{" "}
{this.props.id}
<input
type="checkbox"
name={this.props.id}
value={this.props.id}
id={this.props.id}
onChange={this.handleChange}
checked={isChecked}
disabled={disabled}
/>
</label>
</div>
);
}
}
Whenever any Button component is clicked, the Parent component gets the child Button's id value and puts it into its numbers state array. Whenever a Button is unchecked, the Parent updates is numbers state by removing the id of the child Button.
If my code is right, the expected behavior is whenever a Button checkbox is clicked, the Parent numbers state will be updated immediately (adding or removing a number). However, it always updates with one step lag behind.
I know, that the issue is dealing with the React states not being updated instantly, and I've checked similar issues on Stackoverflow. The problem is that I can't figure it out how to make this two components interact with each other in a proper way. What would be the solution for this issue?
Here are three screenshots from codesandbox
If you want to play with it please find the link https://codesandbox.io/s/w2q8ypnxjw
What I did was, I basically copied and pasted your code and updated setNum function to reflect the changes Think-Twice suggested
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums }, () => {
console.log("state logged inside else if", this.state.numbers);
});
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
So before going further let's quickly address a couple of things regarding to React and setState
As B12Toaster mentioned and provided a link which contains a
quote from official documentation
setState() does not always immediately update the component. It may
batch or defer the update until later.
Think-Twice's also points out that by stating
Basically setState is asynchronous in React. When you modify a value
using setState you will be able to see the updated value only in
render..
So if you want to see the immediate state change in a place which
you trigger setState, you can make use of a call back function as
such setState(updater[, callback])
There are two approaches when it comes to and updater with setState,
you could either pass an object, or you could pass a function So in
Think-Twice's example, an object is passed as an updater
this.setState({ numbers: nums } //updater, () => {
console.log(this.state.numbers); //this will print the updated value here
});
When a function is used as an updater (in your setNum function you
already do that), the callback function can be utilized like below
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
}
Your current implementation and communication structure seems fine. It is actually called Lifting State Up which is recommended also by official documentation.
Basically you store the state of array numbers in a parent component (which can be considered as the source of truth) and you pass the method that changes the state as a prop to it's child component.
In the codesandbox link I provided, the functionalities works the way I expect (at least this is what I expect from your code)
Basically setState is asynchronous in React. When you modify a value using setState you will be able to see the updated value only in render. But to see updated state value immediately you need to do something like below
this.setState({ numbers: nums }, () => {
console.log(this.state.numbers); //this will print the updated value here
});
I would like to allow a user to enter changes in an input field without propagating them to the parent. I did this by returning out of the onChange function whenever I don't want to propagate. However this seems to undo the character I typed.
Here is a use case. I have a number field. I want to trigger onChange on parent when there is a number entered, but ignore "." and ","s (formatters.staticToFloat removes them).
export default class NumberField extends React.Component {
render () {
var props = this.props;
var format = props.formatter || formatters.number;
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={!_.isUndefined(props.value) ? format(props.value) : null}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
//they added a . or , we don't propagate change
if (this.props.value === numValue) {
return;
}
if (this.props.onChange) {
this.props.onChange({
value: numValue,
valid: validation.isValid(numValue, this.props.validation)
});
}
}
};
So far the best way I've come up with is maintaining a separate formattedValue state that only gets set when I want to override the default formatting. It works, but seems like a super dirty solution.
export default class NumberField extends React.Component {
constructor () {
super();
this.state = {
formattedValue: null
};
}
render () {
var props = this.props;
var state = this.state;
var format = props.formatter || formatters.number;
var inputValue = state.formattedValue || (
!_.isUndefined(props.value) ?
format(props.value) :
null
);
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={inputValue}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
//they added a . or , we don't propagate change
if (this.props.value === numValue) {
this.setState({
formattedValue: e.target.value
});
} else {
this.setState({
formattedValue: null
});
}
if (this.props.onChange) {
this.props.onChange({
value: numValue,
valid: validation.isValid(numValue, this.props.validation)
});
}
}
};
Sounds like a case for using state to store the formatted value inside the component, and use a special variant of setState with a callback.
all user input (including . and ,) is put in state and rendered in input field back to user.
only when the format is correct, the parent onChange() is called.
The parent may actually do a re-render triggered by the onChange() call. Therefore, we need to make sure that the last entered character is updated in state, and only after that the parent's onChange() will be called.
Your component would look like as follows:
export default class NumberField extends React.Component {
constructor (props) {
super(props);
this.state = {
value: !_.isUndefined(props.value) ?
props.formatter ? props.formatter(props.value).toString() : formatters.number(props.value).toString()
: null;
};
}
render () {
var props = this.props;
var state = this.state;
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={state.value}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
// if numValue is different from current state
// then it must be an OK update,
// so we update state AND call parent if function exists
if (numValue.toString() != this.state.value) {
this.setState({
value: numValue
},
this.callParent // here is the magic: we pass a callback, to be called after state update and after re-render
);
} else {
// otherwise we only update state (to display invalid character)
this.setState({
value: numValue
});
}
}
callParent() {
// state is updated and component has re-rendered when this is called
// so we can use state.value to inform parent
if (this.props.onChange) {
this.props.onChange({
value: this.state.value,
valid: validation.isValid(this.state.value, this.props.validation)
});
}
}
};
This may be a bit of overkill: you keep a state (formatted value) that you also communicate to the parent. If your parent ALWAYS passes down the newly forwarded input, then you could make your component a lot simpler:
No state, but simply re-render based on props.
You only really need state if the value presented to the user in the input field can deviate from what you communicate to parent.